Add MSAL Authentication Library support (#21024)

This commit is contained in:
Christopher Suh
2022-11-23 17:06:44 -05:00
committed by GitHub
parent fba47815e2
commit 86c3f315f2
32 changed files with 1502 additions and 320 deletions

View File

@@ -13,7 +13,6 @@ import providerSettings from '../../../account-provider/providerSettings';
import { AzureResource } from 'azdata';
import { AxiosResponse } from 'axios';
let azureAuthCodeGrant: TypeMoq.IMock<AzureAuthCodeGrant>;
// let azureDeviceCode: TypeMoq.IMock<AzureDeviceCode>;
@@ -52,9 +51,21 @@ describe('Azure Authentication', function () {
mockAccount = {
isStale: false,
displayInfo: {
contextualDisplayName: 'test',
accountType: 'test',
displayName: 'test',
userId: 'test'
},
key: {
providerId: 'test',
accountId: 'test'
},
properties: {
owningTenant: mockTenant,
tenants: [mockTenant]
tenants: [mockTenant],
providerSettings: provider,
isMsAccount: true
}
} as AzureAccount;
@@ -68,7 +79,7 @@ describe('Azure Authentication', function () {
it('accountHydration should yield a valid account', async function () {
azureAuthCodeGrant.setup(x => x.getTenants(mockToken)).returns((): Promise<Tenant[]> => {
azureAuthCodeGrant.setup(x => x.getTenantsAdal(mockToken)).returns((): Promise<Tenant[]> => {
return Promise.resolve([
mockTenant
]);
@@ -83,30 +94,30 @@ describe('Azure Authentication', function () {
describe('getAccountSecurityToken', function () {
it('should be undefined on stale account', async function () {
mockAccount.isStale = true;
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, TypeMoq.It.isAny(), TypeMoq.It.isAny());
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, TypeMoq.It.isAny(), TypeMoq.It.isAny());
should(securityToken).be.undefined();
});
it('dont find correct resources', async function () {
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, TypeMoq.It.isAny(), -1);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, TypeMoq.It.isAny(), -1);
should(securityToken).be.undefined();
});
it('incorrect tenant', async function () {
await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, 'invalid_tenant', AzureResource.MicrosoftResourceManagement).should.be.rejected();
await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, 'invalid_tenant', AzureResource.MicrosoftResourceManagement).should.be.rejected();
});
it('token recieved for ossRdbmns resource', async function () {
azureAuthCodeGrant.setup(x => x.getTenants(mockToken)).returns(() => {
azureAuthCodeGrant.setup(x => x.getTenantsAdal(mockToken)).returns(() => {
return Promise.resolve([
mockTenant
]);
});
azureAuthCodeGrant.setup(x => x.getTokenHelper(mockTenant, provider.settings.ossRdbmsResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
azureAuthCodeGrant.setup(x => x.getTokenHelperAdal(mockTenant, provider.settings.ossRdbmsResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
azureAuthCodeGrant.setup(x => x.refreshToken(mockTenant, provider.settings.ossRdbmsResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
azureAuthCodeGrant.setup(x => x.refreshTokenAdal(mockTenant, provider.settings.ossRdbmsResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
const mockToken: AccessToken = JSON.parse(JSON.stringify(mockAccessToken));
delete (mockToken as any).invalidData;
return Promise.resolve({
@@ -114,7 +125,7 @@ describe('Azure Authentication', function () {
} as OAuthTokenResponse);
});
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.ossRdbmsResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.ossRdbmsResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
@@ -122,21 +133,21 @@ describe('Azure Authentication', function () {
});
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.OssRdbms);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.OssRdbms);
should(securityToken?.token).be.equal(mockAccessToken.token, 'Token are not similar');
});
it('saved token exists and can be reused', async function () {
delete (mockAccessToken as any).tokenType;
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
expiresOn: `${(new Date().getTime() / 1000) + (10 * 60)}`
});
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
should(securityToken?.tokenType).be.equal('Bearer', 'tokenType should be bearer on a successful getSecurityToken from cache');
});
@@ -145,47 +156,47 @@ describe('Azure Authentication', function () {
it('saved token had invalid expiration', async function () {
delete (mockAccessToken as any).tokenType;
(mockAccessToken as any).invalidData = 'this should not exist on response';
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
expiresOn: 'invalid'
});
});
azureAuthCodeGrant.setup(x => x.refreshToken(mockTenant, provider.settings.microsoftResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
azureAuthCodeGrant.setup(x => x.refreshTokenAdal(mockTenant, provider.settings.microsoftResource!, mockRefreshToken)).returns((): Promise<OAuthTokenResponse> => {
const mockToken: AccessToken = JSON.parse(JSON.stringify(mockAccessToken));
delete (mockToken as any).invalidData;
return Promise.resolve({
accessToken: mockToken
} as OAuthTokenResponse);
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
should((securityToken as any).invalidData).be.undefined(); // Ensure its a new one
should(securityToken?.tokenType).be.equal('Bearer', 'tokenType should be bearer on a successful getSecurityToken from cache');
azureAuthCodeGrant.verify(x => x.refreshToken(mockTenant, provider.settings.microsoftResource!, mockRefreshToken), TypeMoq.Times.once());
azureAuthCodeGrant.verify(x => x.refreshTokenAdal(mockTenant, provider.settings.microsoftResource!, mockRefreshToken), TypeMoq.Times.once());
});
describe('no saved token', function () {
it('no base token', async function () {
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
return Promise.resolve(undefined);
});
azureAuthCodeGrant.setup(x => x.getSavedToken(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
return Promise.resolve(undefined);
});
await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement).should.be.rejected();
await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement).should.be.rejected();
});
it('base token exists', async function () {
azureAuthCodeGrant.setup(x => x.getSavedToken(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(mockTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string } | undefined> => {
return Promise.resolve(undefined);
});
azureAuthCodeGrant.setup(x => x.getSavedToken(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
azureAuthCodeGrant.setup(x => x.getSavedTokenAdal(azureAuthCodeGrant.object.commonTenant, provider.settings.microsoftResource!, mockAccount.key)).returns((): Promise<{ accessToken: AccessToken, refreshToken: RefreshToken, expiresOn: string }> => {
return Promise.resolve({
accessToken: mockAccessToken,
refreshToken: mockRefreshToken,
@@ -194,13 +205,13 @@ describe('Azure Authentication', function () {
});
delete (mockAccessToken as any).tokenType;
azureAuthCodeGrant.setup(x => x.refreshToken(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
azureAuthCodeGrant.setup(x => x.refreshTokenAdal(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityToken(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, mockTenant.id, AzureResource.MicrosoftResourceManagement);
should(securityToken?.tokenType).be.equal('Bearer', 'tokenType should be bearer on a successful getSecurityToken from cache');
});
});
@@ -218,16 +229,16 @@ describe('Azure Authentication', function () {
} as AxiosResponse<any>);
});
azureAuthCodeGrant.setup(x => x.handleInteractionRequired(mockTenant, provider.settings.microsoftResource!)).returns(() => {
azureAuthCodeGrant.setup(x => x.handleInteractionRequiredAdal(mockTenant, provider.settings.microsoftResource!)).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
const result = await azureAuthCodeGrant.object.getToken(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
const result = await azureAuthCodeGrant.object.getTokenAdal(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
azureAuthCodeGrant.verify(x => x.handleInteractionRequired(mockTenant, provider.settings.microsoftResource!), TypeMoq.Times.once());
azureAuthCodeGrant.verify(x => x.handleInteractionRequiredAdal(mockTenant, provider.settings.microsoftResource!), TypeMoq.Times.once());
should(result?.accessToken).be.deepEqual(mockAccessToken);
});
@@ -241,7 +252,7 @@ describe('Azure Authentication', function () {
} as AxiosResponse<any>);
});
await azureAuthCodeGrant.object.getToken(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData).should.be.rejected();
await azureAuthCodeGrant.object.getTokenAdal(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData).should.be.rejected();
});
it('calls getTokenHelper', async function () {
@@ -255,16 +266,16 @@ describe('Azure Authentication', function () {
} as AxiosResponse<any>);
});
azureAuthCodeGrant.setup(x => x.getTokenHelper(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
azureAuthCodeGrant.setup(x => x.getTokenHelperAdal(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
return Promise.resolve({
accessToken: mockAccessToken
} as OAuthTokenResponse);
});
const result = await azureAuthCodeGrant.object.getToken(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
const result = await azureAuthCodeGrant.object.getTokenAdal(mockTenant, provider.settings.microsoftResource!, {} as TokenPostData);
azureAuthCodeGrant.verify(x => x.getTokenHelper(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
azureAuthCodeGrant.verify(x => x.getTokenHelperAdal(mockTenant, provider.settings.microsoftResource!, TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
should(result?.accessToken).be.deepEqual(mockAccessToken);
});

View File

@@ -28,9 +28,11 @@ import allSettings from '../../../account-provider/providerSettings';
// Mock services
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionServiceADAL: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionServiceMSAL: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
let mockAppContext: AppContext;
let mockAppContextADAL: AppContext;
let mockAppContextMSAL: AppContext;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
@@ -95,18 +97,25 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, undefined)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionServiceMSAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = [];
mockAppContext = new AppContext(mockExtensionContext.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContextADAL = new AppContext(mockExtensionContext.object);
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContextMSAL = new AppContext(mockExtensionContext.object);
mockAppContextMSAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceMSAL.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
@@ -120,8 +129,8 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
sinon.restore();
});
it('Should be correct when created.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
it('Should be correct when created for ADAL.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
@@ -140,14 +149,34 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
should(nodeInfo.iconType).equal(AzureResourceItemType.account);
});
it('Should be correct when there are subscriptions listed.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
it('Should be correct when created for MSAL.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
should(accountTreeNode.nodePathValue).equal(accountTreeNodeId);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.id).equal(accountTreeNodeId);
should(treeItem.label).equal(mockAccount.displayInfo.displayName);
should(treeItem.contextValue).equal(AzureResourceItemType.account);
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(mockAccount.displayInfo.displayName);
should(nodeInfo.isLeaf).false();
should(nodeInfo.nodeType).equal(AzureResourceItemType.account);
should(nodeInfo.iconType).equal(AzureResourceItemType.account);
});
it('Should be correct when there are subscriptions listed for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
@@ -161,13 +190,34 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should only show subscriptions with valid tokens.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
it('Should be correct when there are subscriptions listed for MSAL.', async function (): Promise<void> {
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(mockSubscriptions.length);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should only show subscriptions with valid tokens for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').onFirstCall().resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
@@ -181,13 +231,53 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should be correct when there are subscriptions filtered.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
it('Should only show subscriptions with valid tokens for MSAL.', async function (): Promise<void> {
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').onFirstCall().resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(1);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should be correct when there are subscriptions filtered for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
const nodeInfo = accountTreeNode.getNodeInfo();
should(nodeInfo.label).equal(accountTreeNodeLabel);
});
it('Should be correct when there are subscriptions filtered for MSAL.', async function (): Promise<void> {
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
const subscriptionNodes = await accountTreeNode.getChildren();
@@ -206,17 +296,23 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceMSAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = [];
mockAppContext = new AppContext(mockExtensionContext.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContextADAL = new AppContext(mockExtensionContext.object);
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContextMSAL = new AppContext(mockExtensionContext.object);
mockAppContextMSAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceMSAL.object);
mockAppContextMSAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
@@ -231,15 +327,15 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
sinon.restore();
});
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
it('Should load subscriptions from scratch and update cache when it is clearing cache for ADAL.', async function (): Promise<void> {
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
@@ -265,16 +361,16 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should load subscriptions from cache when it is not clearing cache.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
await accountTreeNode.getChildren();
const children = await accountTreeNode.getChildren();
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
@@ -286,9 +382,9 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should handle when there is no subscriptions.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve([]));
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve([]));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
@@ -302,10 +398,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should honor subscription filtering.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
@@ -320,16 +416,16 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
});
it('Should handle errors.', async function (): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
const mockError = 'Test error';
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => { throw new Error(mockError); });
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
@@ -346,17 +442,17 @@ describe('AzureResourceAccountTreeNode.clearCache', function (): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = [];
mockAppContext = new AppContext(mockExtensionContext.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContextADAL = new AppContext(mockExtensionContext.object);
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
sinon.stub(azdata.accounts, 'getAccountSecurityToken').returns(Promise.resolve(mockToken));
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
@@ -372,7 +468,7 @@ describe('AzureResourceAccountTreeNode.clearCache', function (): void {
});
it('Should clear cache.', async function (): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
accountTreeNode.clearCache();
should(accountTreeNode.isClearingCache).true();
});

View File

@@ -26,10 +26,11 @@ let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
// Mock test data
const mockAccount1: AzureAccount = {
const mockAccountAdal1: AzureAccount = {
key: {
accountId: 'mock_account_1',
providerId: 'mock_provider'
providerId: 'mock_provider',
authLibrary: 'ADAL'
},
displayInfo: {
displayName: 'mock_account_1@test.com',
@@ -40,7 +41,7 @@ const mockAccount1: AzureAccount = {
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccount2: AzureAccount = {
const mockAccountAdal2: AzureAccount = {
key: {
accountId: 'mock_account_2',
providerId: 'mock_provider'
@@ -54,7 +55,39 @@ const mockAccount2: AzureAccount = {
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccounts = [mockAccount1, mockAccount2];
const mockAccountsADAL = [mockAccountAdal1, mockAccountAdal2];
const mockAccountMsal1: AzureAccount = {
key: {
accountId: 'mock_account_1',
providerId: 'mock_provider',
authLibrary: 'MSAL'
},
displayInfo: {
displayName: 'mock_account_1@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccountMsal2: AzureAccount = {
key: {
accountId: 'mock_account_2',
providerId: 'mock_provider',
authLibrary: 'MSAL'
},
displayInfo: {
displayName: 'mock_account_2@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: TypeMoq.Mock.ofType<AzureAccountProperties>().object,
isStale: false
};
const mockAccountsMSAL = [mockAccountMsal1, mockAccountMsal2];
describe('AzureResourceTreeProvider.getChildren', function (): void {
beforeEach(() => {
@@ -68,35 +101,69 @@ describe('AzureResourceTreeProvider.getChildren', function (): void {
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
});
afterEach(function(): void {
afterEach(function (): void {
sinon.restore();
});
it('Should load accounts.', async function (): Promise<void> {
const getAllAccountsStub = sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve(mockAccounts));
it('Should load accounts for ADAL', async function (): Promise<void> {
const getAllAccountsStub = sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve(mockAccountsADAL));
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'ADAL');
await treeProvider.getChildren(undefined); // Load account promise
const children = await treeProvider.getChildren(undefined); // Actual accounts
should(getAllAccountsStub.calledOnce).be.true('getAllAccounts should have been called exactly once');
should(children).Array();
should(children.length).equal(mockAccounts.length);
should(children.length).equal(mockAccountsADAL.length);
for (let ix = 0; ix < mockAccounts.length; ix++) {
for (let ix = 0; ix < mockAccountsADAL.length; ix++) {
const child = children[ix];
const account = mockAccounts[ix];
const account = mockAccountsADAL[ix];
should(child).instanceof(AzureResourceAccountTreeNode);
should(child.nodePathValue).equal(`account_${account.key.accountId}`);
}
});
it('Should handle when there is no accounts.', async function (): Promise<void> {
it('Should load accounts for MSAL', async function (): Promise<void> {
const getAllAccountsStub = sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve(mockAccountsMSAL));
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'MSAL');
await treeProvider.getChildren(undefined); // Load account promise
const children = await treeProvider.getChildren(undefined); // Actual accounts
should(getAllAccountsStub.calledOnce).be.true('getAllAccounts should have been called exactly once');
should(children).Array();
should(children.length).equal(mockAccountsMSAL.length);
for (let ix = 0; ix < mockAccountsMSAL.length; ix++) {
const child = children[ix];
const account = mockAccountsMSAL[ix];
should(child).instanceof(AzureResourceAccountTreeNode);
should(child.nodePathValue).equal(`account_${account.key.accountId}`);
}
});
it('Should handle when there is no accounts for ADAL', async function (): Promise<void> {
sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve([]));
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'ADAL');
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);
should(children).Array();
should(children.length).equal(1);
should(children[0]).instanceof(AzureResourceAccountNotSignedInTreeNode);
});
it('Should handle when there is no accounts for MSAL', async function (): Promise<void> {
sinon.stub(azdata.accounts, 'getAllAccounts').returns(Promise.resolve([]));
const treeProvider = new AzureResourceTreeProvider(mockAppContext, 'MSAL');
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);