diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 9cb421db4d..e4c28766e0 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -307,6 +307,10 @@ "command": "sfdx.force.auth.logout.all", "when": "sfdx:project_opened" }, + { + "command": "sfdx.force.auth.logout.default", + "when": "sfdx:project_opened && sfdx:has_default_username" + }, { "command": "sfdx.force.org.create", "when": "sfdx:project_opened" @@ -570,6 +574,10 @@ "command": "sfdx.force.auth.logout.all", "title": "%force_auth_logout_all_text%" }, + { + "command": "sfdx.force.auth.logout.default", + "title": "%force_auth_logout_default_text%" + }, { "command": "sfdx.force.org.create", "title": "%force_org_create_default_scratch_org_text%" diff --git a/packages/salesforcedx-vscode-core/package.nls.ja.json b/packages/salesforcedx-vscode-core/package.nls.ja.json index 4ac7f27be8..73d499dfa5 100644 --- a/packages/salesforcedx-vscode-core/package.nls.ja.json +++ b/packages/salesforcedx-vscode-core/package.nls.ja.json @@ -4,6 +4,7 @@ "force_auth_web_login_authorize_org_text": "SFDX: 組織を認証", "force_auth_access_token_authorize_org_text": "SFDX: Authorize an Org using Session ID", "force_auth_logout_all_text": "SFDX: すべての認証済み組織からログアウト", + "force_auth_logout_default_text": "SFDX: Log Out from Default Org", "force_org_create_default_scratch_org_text": "SFDX: デフォルトのスクラッチ組織を作成...", "force_org_open_default_scratch_org_text": "SFDX: デフォルトの組織を開く", "force_source_pull_default_scratch_org_text": "SFDX: デフォルトのスクラッチ組織からソースをプル", diff --git a/packages/salesforcedx-vscode-core/package.nls.json b/packages/salesforcedx-vscode-core/package.nls.json index 8ce0c57890..545fc7c8b1 100644 --- a/packages/salesforcedx-vscode-core/package.nls.json +++ b/packages/salesforcedx-vscode-core/package.nls.json @@ -4,6 +4,7 @@ "force_auth_web_login_authorize_org_text": "SFDX: Authorize an Org", "force_auth_access_token_authorize_org_text": "SFDX: Authorize an Org using Session ID", "force_auth_logout_all_text": "SFDX: Log Out from All Authorized Orgs", + "force_auth_logout_default_text": "SFDX: Log Out from Default Org", "force_org_create_default_scratch_org_text": "SFDX: Create a Default Scratch Org...", "force_org_open_default_scratch_org_text": "SFDX: Open Default Org", "force_source_pull_default_scratch_org_text": "SFDX: Pull Source from Default Scratch Org", diff --git a/packages/salesforcedx-vscode-core/src/commands/auth/authParamsGatherer.ts b/packages/salesforcedx-vscode-core/src/commands/auth/authParamsGatherer.ts index 67d8140f84..87d5cd6fd2 100644 --- a/packages/salesforcedx-vscode-core/src/commands/auth/authParamsGatherer.ts +++ b/packages/salesforcedx-vscode-core/src/commands/auth/authParamsGatherer.ts @@ -192,3 +192,33 @@ export class AccessTokenParamsGatherer }; } } + +export class ScratchOrgLogoutParamsGatherer + implements ParametersGatherer { + public constructor( + public readonly username: string, + public readonly alias?: string + ) {} + + public async gather(): Promise> { + const prompt = nls.localize( + 'auth_logout_scratch_prompt', + this.alias || this.username + ); + const logoutResponse = nls.localize('auth_logout_scratch_logout'); + + const confirm = await vscode.window.showInformationMessage( + prompt, + { modal: true }, + ...[logoutResponse] + ); + if (confirm !== logoutResponse) { + return { type: 'CANCEL' }; + } + + return { + type: 'CONTINUE', + data: this.username + }; + } +} diff --git a/packages/salesforcedx-vscode-core/src/commands/auth/forceAuthLogout.ts b/packages/salesforcedx-vscode-core/src/commands/auth/forceAuthLogout.ts index 66640f0ab7..6662e1e161 100644 --- a/packages/salesforcedx-vscode-core/src/commands/auth/forceAuthLogout.ts +++ b/packages/salesforcedx-vscode-core/src/commands/auth/forceAuthLogout.ts @@ -5,17 +5,28 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { AuthRemover } from '@salesforce/core'; +import { LibraryCommandletExecutor } from '@salesforce/salesforcedx-utils-vscode/out/src'; import { Command, SfdxCommandBuilder } from '@salesforce/salesforcedx-utils-vscode/out/src/cli'; +import { notificationService } from '@salesforce/salesforcedx-utils-vscode/out/src/commands'; +import { ContinueResponse } from '@salesforce/salesforcedx-utils-vscode/out/src/types'; +import { CancellationToken, Progress } from 'vscode'; +import { OUTPUT_CHANNEL } from '../../channels'; import { nls } from '../../messages'; +import { telemetryService } from '../../telemetry'; +import { OrgAuthInfo } from '../../util'; +import { forceConfigSet } from '../forceConfigSet'; import { EmptyParametersGatherer, SfdxCommandlet, SfdxCommandletExecutor, - SfdxWorkspaceChecker + SfdxWorkspaceChecker, + SimpleGatherer } from '../util'; +import { ScratchOrgLogoutParamsGatherer } from './authParamsGatherer'; export class ForceAuthLogoutAll extends SfdxCommandletExecutor<{}> { public static withoutShowingChannel(): ForceAuthLogoutAll { @@ -47,3 +58,81 @@ const commandlet = new SfdxCommandlet( export async function forceAuthLogoutAll() { await commandlet.run(); } + +export class AuthLogoutDefault extends LibraryCommandletExecutor { + constructor() { + super( + nls.localize('force_auth_logout_default_text'), + 'force_auth_logout_default', + OUTPUT_CHANNEL + ); + } + + public async run( + response: ContinueResponse, + progress?: Progress<{ + message?: string | undefined; + increment?: number | undefined; + }>, + token?: CancellationToken + ): Promise { + try { + await removeUsername(response.data); + } catch (e) { + telemetryService.sendException(e.name, e.message); + return false; + } + return true; + } +} + +export async function forceAuthLogoutDefault() { + const { username, isScratch, alias, error } = await resolveDefaultUsername(); + if (error) { + telemetryService.sendException(error.name, error.message); + notificationService.showErrorMessage('Logout failed to run'); + } else if (username) { + // confirm logout for scratch orgs due to special considerations: + // https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_logout.htm + const logoutCommandlet = new SfdxCommandlet( + new SfdxWorkspaceChecker(), + isScratch + ? new ScratchOrgLogoutParamsGatherer(username, alias) + : new SimpleGatherer(username), + new AuthLogoutDefault() + ); + await logoutCommandlet.run(); + } else { + notificationService.showInformationMessage( + nls.localize('auth_logout_no_default_org') + ); + } +} + +async function removeUsername(username: string) { + await forceConfigSet(''); + const authRemover = await AuthRemover.create(); + await authRemover.removeAuth(username); +} + +async function resolveDefaultUsername(): Promise<{ + username?: string; + isScratch: boolean; + alias?: string; + error?: Error; +}> { + const usernameOrAlias = await OrgAuthInfo.getDefaultUsernameOrAlias(false); + if (usernameOrAlias) { + const username = await OrgAuthInfo.getUsername(usernameOrAlias); + const alias = username !== usernameOrAlias ? usernameOrAlias : undefined; + let isScratch = false; + + try { + isScratch = await OrgAuthInfo.isAScratchOrg(username); + } catch (err) { + return { error: err, isScratch: false }; + } + return { username, isScratch, alias }; + } + return { isScratch: false }; +} diff --git a/packages/salesforcedx-vscode-core/src/commands/index.ts b/packages/salesforcedx-vscode-core/src/commands/index.ts index 398427b501..d28fafe41e 100644 --- a/packages/salesforcedx-vscode-core/src/commands/index.ts +++ b/packages/salesforcedx-vscode-core/src/commands/index.ts @@ -133,7 +133,11 @@ export { turnOffLogging, ForceStopApexDebugLoggingExecutor } from './forceStopApexDebugLogging'; -export { forceAuthLogoutAll, ForceAuthLogoutAll } from './auth/forceAuthLogout'; +export { + forceAuthLogoutAll, + ForceAuthLogoutAll, + forceAuthLogoutDefault +} from './auth/forceAuthLogout'; import { DeveloperLogTraceFlag } from '../traceflag/developerLogTraceFlag'; export const developerLogTraceFlag = DeveloperLogTraceFlag.getInstance(); export { forceConfigSet, ForceConfigSetExecutor } from './forceConfigSet'; diff --git a/packages/salesforcedx-vscode-core/src/context/workspaceOrgType.ts b/packages/salesforcedx-vscode-core/src/context/workspaceOrgType.ts index e295f83c43..47ac6db916 100644 --- a/packages/salesforcedx-vscode-core/src/context/workspaceOrgType.ts +++ b/packages/salesforcedx-vscode-core/src/context/workspaceOrgType.ts @@ -39,6 +39,7 @@ export function setWorkspaceOrgTypeWithOrgType(orgType: OrgType) { export async function setupWorkspaceOrgType(defaultUsernameOrAlias?: string) { try { + setHasDefaultUsername(!!defaultUsernameOrAlias); const orgType = await getWorkspaceOrgType(defaultUsernameOrAlias); setWorkspaceOrgTypeWithOrgType(orgType); } catch (e) { @@ -77,6 +78,14 @@ function setDefaultUsernameHasNoChangeTracking(val: boolean) { ); } +function setHasDefaultUsername(val: boolean) { + vscode.commands.executeCommand( + 'setContext', + 'sfdx:has_default_username', + val + ); +} + export async function getDefaultUsernameOrAlias(): Promise { if (hasRootWorkspace()) { return await OrgAuthInfo.getDefaultUsernameOrAlias(true); diff --git a/packages/salesforcedx-vscode-core/src/index.ts b/packages/salesforcedx-vscode-core/src/index.ts index 7c210e7352..64d9f251be 100644 --- a/packages/salesforcedx-vscode-core/src/index.ts +++ b/packages/salesforcedx-vscode-core/src/index.ts @@ -15,6 +15,7 @@ import { forceAuthAccessToken, forceAuthDevHub, forceAuthLogoutAll, + forceAuthLogoutDefault, forceAuthWebLogin, forceConfigList, forceConfigSet, @@ -79,8 +80,15 @@ import { SfdxCommandletExecutor, SfdxWorkspaceChecker } from './commands/util'; -import { PersistentStorageService, registerConflictView, setupConflictView } from './conflict'; -import { ENABLE_SOBJECT_REFRESH_ON_STARTUP, SFDX_CORE_CONFIGURATION_NAME } from './constants'; +import { + PersistentStorageService, + registerConflictView, + setupConflictView +} from './conflict'; +import { + ENABLE_SOBJECT_REFRESH_ON_STARTUP, + SFDX_CORE_CONFIGURATION_NAME +} from './constants'; import { getDefaultUsernameOrAlias } from './context'; import { workspaceContext } from './context'; import * as decorators from './decorators'; @@ -115,6 +123,10 @@ function registerCommands( 'sfdx.force.auth.logout.all', forceAuthLogoutAll ); + const forceAuthLogoutDefaultCmd = vscode.commands.registerCommand( + 'sfdx.force.auth.logout.default', + forceAuthLogoutDefault + ); const forceOrgCreateCmd = vscode.commands.registerCommand( 'sfdx.force.org.create', forceOrgCreate @@ -368,6 +380,7 @@ function registerCommands( forceAuthWebLoginCmd, forceAuthDevHubCmd, forceAuthLogoutAllCmd, + forceAuthLogoutDefaultCmd, forceDataSoqlQueryInputCmd, forceDataSoqlQuerySelectionCmd, forceDiffFile, diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ja.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ja.ts index e55dba05a9..71c924f4a2 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ja.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ja.ts @@ -213,6 +213,7 @@ export const messages = { demo_mode_prompt: 'デモモードまたは共有マシンで、ビジネスまたは本番組織を認証することは推奨されません。認証を続ける場合、組織を使用した後、必ず "SFDX: すべての認証済み組織からログアウト" を実行してください。', force_auth_logout_all_text: 'SFDX: すべての認証済み組織からログアウト', + force_auth_logout_default_text: 'SFDX: Log Out from Default Org', manifest_editor_title_message: 'マニフェストエディタ', REST_API: 'REST API', tooling_API: 'Tooling API', @@ -232,6 +233,10 @@ export const messages = { auth_custom_label: 'カスタム', auth_custom_detail: 'カスタムログイン URL を入力', auth_invalid_url: 'URL は http:// か https:// で始める必要があります。', + auth_logout_scratch_prompt: + 'Log out of this scratch org?\n\nBefore logging out, ensure that you or someone on your team has a username and password for %s scratch org. Otherwise you might lose all access to this scratch org.', + auth_logout_scratch_logout: 'Logout', + auth_logout_no_default_org: 'No default org to logout from', error_fetching_auth_info_text: '保存時のプッシュまたはデプロイ実行中にエラー: デフォルトの組織に接続できませんでした。"SFDX: デフォルトのスクラッチ組織を作成" または "SFDX: 組織を認証" を実行して、保存したソースをプッシュまたはデプロイしてください。もしくは、保存時のプッシュまたはデプロイを無効化するため、VS Code のユーザまたはワークスペース設定で "salesforcedx-vscode-core.push-or-deploy-on-save.enabled" を false に設定してください。', error_no_package_directories_found_on_setup_text: diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index 27c0ae3cfb..d7084b390b 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -250,6 +250,7 @@ export const messages = { demo_mode_prompt: 'Authorizing a business or production org is not recommended on a demo or shared machine. If you continue with the authentication, be sure to run "SFDX: Log Out from All Authorized Orgs" when you\'re done using this org.', force_auth_logout_all_text: 'SFDX: Log Out from All Authorized Orgs', + force_auth_logout_default_text: 'SFDX: Log Out from Default Org', manifest_editor_title_message: 'Manifest Editor', REST_API: 'REST API', tooling_API: 'Tooling API', @@ -269,6 +270,10 @@ export const messages = { auth_custom_label: 'Custom', auth_custom_detail: 'Enter a custom login URL', auth_invalid_url: 'URL must begin with http:// or https://', + auth_logout_scratch_prompt: + 'Log out of this scratch org?\n\nBefore logging out, ensure that you or someone on your team has a username and password for %s scratch org. Otherwise you might lose all access to this scratch org.', + auth_logout_scratch_logout: 'Logout', + auth_logout_no_default_org: 'No default org to logout from', error_fetching_auth_info_text: 'Error running push or deploy on save: We couldn\'t connect to your default org. Run "SFDX: Create a Default Scratch Org" or "SFDX: Authorize an Org", then push or deploy the source that you just saved. Or, to disable push or deploy on save, set "salesforcedx-vscode-core.push-or-deploy-on-save.enabled" to false in your user or workspace settings for VS Code.', error_no_package_directories_found_on_setup_text: diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/auth/forceAuthLogout.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/auth/forceAuthLogout.test.ts index 9739b853bb..66e9480eb9 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/auth/forceAuthLogout.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/commands/auth/forceAuthLogout.test.ts @@ -5,9 +5,19 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { Aliases, AuthRemover } from '@salesforce/core'; +import { notificationService } from '@salesforce/salesforcedx-utils-vscode/out/src/commands'; import { expect } from 'chai'; -import { ForceAuthLogoutAll } from '../../../../src/commands'; +import { createSandbox, SinonSandbox, SinonStub } from 'sinon'; +import * as vscode from 'vscode'; +import { + ForceAuthLogoutAll, + forceAuthLogoutDefault +} from '../../../../src/commands'; +import { SfdxCommandlet } from '../../../../src/commands/util'; import { nls } from '../../../../src/messages'; +import { telemetryService } from '../../../../src/telemetry'; +import { OrgAuthInfo } from '../../../../src/util'; // tslint:disable:no-unused-expression describe('Force Auth Logout All', () => { @@ -22,3 +32,207 @@ describe('Force Auth Logout All', () => { ); }); }); + +describe('Force Auth Logout Default', () => { + let sb: SinonSandbox; + let getUsernameStub: SinonStub; + let scratchOrgStub: SinonStub; + let aliasesStub: SinonStub; + let notificationStub: SinonStub; + let sendExceptionStub: SinonStub; + let commandletStub: SinonStub; + let inputMessageStub: SinonStub; + let authRemoverStub: SinonStub; + const alias = 'test user 1'; + const username = 'test-username1@example.com'; + + beforeEach(() => { + sb = createSandbox(); + getUsernameStub = sb.stub(OrgAuthInfo, 'getDefaultUsernameOrAlias'); + scratchOrgStub = sb.stub(OrgAuthInfo, 'isAScratchOrg'); + aliasesStub = sb.stub(Aliases, 'fetch'); + notificationStub = sb.stub(notificationService, 'showInformationMessage'); + sendExceptionStub = sb.stub(telemetryService, 'sendException'); + commandletStub = sb.stub(SfdxCommandlet.prototype, 'run'); + inputMessageStub = sb.stub(vscode.window, 'showInformationMessage'); + authRemoverStub = sb.stub(AuthRemover, 'create'); + }); + + afterEach(() => { + sb.restore(); + }); + + it('Should post a message for no default org', async () => { + getUsernameStub.resolves(undefined); + scratchOrgStub.resolves(false); + notificationStub.resolves(); + + await forceAuthLogoutDefault(); + + expect( + getUsernameStub.called, + 'should have requested default username' + ).to.equal(true); + expect( + sendExceptionStub.called, + 'should not have reported an error' + ).to.equal(false); + expect(notificationStub.called, 'should have posted a message').to.equal( + true + ); + const notificationArgs = notificationStub.getCall(0).args; + expect(notificationArgs[0]).to.equal('No default org to logout from'); + }); + + it('Should handle an aliased org', async () => { + getUsernameStub.resolves(alias); + aliasesStub.resolves(username); + scratchOrgStub.resolves(false); + notificationStub.resolves(); + + await forceAuthLogoutDefault(); + + expect( + getUsernameStub.called, + 'should have requested default username' + ).to.equal(true); + expect(aliasesStub.called, 'should have translated an alias').to.equal( + true + ); + const aliasArgs = aliasesStub.getCall(0).args; + expect(aliasArgs[0]).to.equal(alias); + expect( + sendExceptionStub.called, + 'should not have reported an error' + ).to.equal(false); + expect( + notificationStub.called, + 'should not have posted an error message' + ).to.equal(false); + expect(commandletStub.called).to.equal(true); + }); + + it('Should handle an un-aliased org', async () => { + getUsernameStub.resolves(username); + aliasesStub.resolves(undefined); + scratchOrgStub.resolves(false); + notificationStub.resolves(); + + await forceAuthLogoutDefault(); + + expect( + getUsernameStub.called, + 'should have requested default username' + ).to.equal(true); + expect(aliasesStub.called, 'should have translated an alias').to.equal( + true + ); + const aliasArgs = aliasesStub.getCall(0).args; + expect(aliasArgs[0]).to.equal(username); + expect( + sendExceptionStub.called, + 'should not have reported an error' + ).to.equal(false); + expect( + notificationStub.called, + 'should not have posted an error message' + ).to.equal(false); + expect(commandletStub.called).to.equal(true); + }); + + it('Should post a choice for a scratch org', async () => { + getUsernameStub.resolves(username); + aliasesStub.resolves(undefined); + scratchOrgStub.resolves(true); + notificationStub.resolves(); + + await forceAuthLogoutDefault(); + + expect( + getUsernameStub.called, + 'should have requested default username' + ).to.equal(true); + expect( + aliasesStub.called, + 'should have attempted alias translation' + ).to.equal(true); + const aliasArgs = aliasesStub.getCall(0).args; + expect(aliasArgs[0]).to.equal(username); + expect( + sendExceptionStub.called, + 'should not have reported an error' + ).to.equal(false); + expect( + notificationStub.called, + 'should not have posted an error message' + ).to.equal(false); + expect(commandletStub.called).to.equal(true); + }); + + it('Should allow logout cancel for a scratch org', async () => { + getUsernameStub.resolves(username); + aliasesStub.resolves(undefined); + scratchOrgStub.resolves(true); + notificationStub.resolves(); + inputMessageStub.returns(undefined); + commandletStub.restore(); + + await forceAuthLogoutDefault(); + + expect( + sendExceptionStub.called, + 'should not have reported an error' + ).to.equal(false); + expect( + notificationStub.called, + 'should not have posted an error message' + ).to.equal(false); + expect(inputMessageStub.called, 'should have prompted a message').to.equal( + true + ); + const messageArgs = inputMessageStub.getCall(0).args; + expect(messageArgs).to.deep.equal([ + nls.localize('auth_logout_scratch_prompt', username), + { modal: true }, + nls.localize('auth_logout_scratch_logout') + ]); + }); + + it('Should allow logout for a scratch org', async () => { + let removedUsername; + const logoutResponse = nls.localize('auth_logout_scratch_logout'); + + getUsernameStub.resolves(username); + aliasesStub.resolves(undefined); + scratchOrgStub.resolves(true); + notificationStub.resolves(); + inputMessageStub.returns(logoutResponse); + commandletStub.restore(); + authRemoverStub.resolves({ + removeAuth: (name: string) => { + removedUsername = name; + } + }); + + await forceAuthLogoutDefault(); + + expect( + sendExceptionStub.called, + 'should not have reported an error' + ).to.equal(false); + expect( + notificationStub.called, + 'should not have posted an error message' + ).to.equal(false); + expect(inputMessageStub.called, 'should have prompted a message').to.equal( + true + ); + const messageArgs = inputMessageStub.getCall(0).args; + expect(messageArgs).to.deep.equal([ + nls.localize('auth_logout_scratch_prompt', username), + { modal: true }, + logoutResponse + ]); + expect(removedUsername, 'should have removed username').to.equal(username); + }); +}); diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/context/workspaceOrgType.test.ts b/packages/salesforcedx-vscode-core/test/vscode-integration/context/workspaceOrgType.test.ts index cce5cada82..1d65672582 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/context/workspaceOrgType.test.ts +++ b/packages/salesforcedx-vscode-core/test/vscode-integration/context/workspaceOrgType.test.ts @@ -145,7 +145,8 @@ describe('setupWorkspaceOrgType', () => { await setupWorkspaceOrgType(defaultUsername); expect(aliasesSpy.called).to.be.false; - expect(executeCommandStub.calledTwice).to.be.true; + expect(executeCommandStub.calledThrice).to.be.true; + expectSetHasDefaultUsername(false, executeCommandStub); expectDefaultUsernameHasChangeTracking(false, executeCommandStub); expectDefaultUsernameHasNoChangeTracking(false, executeCommandStub); @@ -165,7 +166,8 @@ describe('setupWorkspaceOrgType', () => { await setupWorkspaceOrgType(defaultUsername); - expect(executeCommandStub.calledTwice).to.be.true; + expect(executeCommandStub.calledThrice).to.be.true; + expectSetHasDefaultUsername(true, executeCommandStub); expectDefaultUsernameHasChangeTracking(true, executeCommandStub); expectDefaultUsernameHasNoChangeTracking(true, executeCommandStub); @@ -189,7 +191,8 @@ describe('setupWorkspaceOrgType', () => { expect(authInfoCreateStub.getCall(0).args[0]).to.eql({ username: 'scratch@org.com' }); - expect(executeCommandStub.calledTwice).to.be.true; + expect(executeCommandStub.calledThrice).to.be.true; + expectSetHasDefaultUsername(true, executeCommandStub); expectDefaultUsernameHasChangeTracking(true, executeCommandStub); expectDefaultUsernameHasNoChangeTracking(false, executeCommandStub); @@ -207,7 +210,8 @@ describe('setupWorkspaceOrgType', () => { const defaultUsername = 'sandbox@org.com'; await setupWorkspaceOrgType(defaultUsername); - expect(executeCommandStub.calledTwice).to.be.true; + expect(executeCommandStub.calledThrice).to.be.true; + expectSetHasDefaultUsername(true, executeCommandStub); expectDefaultUsernameHasChangeTracking(false, executeCommandStub); expectDefaultUsernameHasNoChangeTracking(true, executeCommandStub); @@ -234,7 +238,8 @@ describe('setupWorkspaceOrgType', () => { await setupWorkspaceOrgType(username); expect(aliasesSpy.called).to.be.true; - expect(executeCommandStub.calledTwice).to.be.true; + expect(executeCommandStub.calledThrice).to.be.true; + expectSetHasDefaultUsername(true, executeCommandStub); expectDefaultUsernameHasChangeTracking(true, executeCommandStub); expectDefaultUsernameHasNoChangeTracking(true, executeCommandStub); } finally { @@ -256,11 +261,22 @@ const getAliasesFetchStub = (returnValue: any) => const getAuthInfoCreateStub = (returnValue: any) => sinon.stub(AuthInfo, 'create').returns(Promise.resolve(returnValue)); +const expectSetHasDefaultUsername = ( + hasUsername: boolean, + executeCommandStub: sinon.SinonStub +) => { + expect(executeCommandStub.getCall(0).args).to.eql([ + 'setContext', + 'sfdx:has_default_username', + hasUsername + ]); +}; + const expectDefaultUsernameHasChangeTracking = ( hasChangeTracking: boolean, executeCommandStub: sinon.SinonStub ) => { - expect(executeCommandStub.getCall(0).args).to.eql([ + expect(executeCommandStub.getCall(1).args).to.eql([ 'setContext', 'sfdx:default_username_has_change_tracking', hasChangeTracking @@ -271,7 +287,7 @@ const expectDefaultUsernameHasNoChangeTracking = ( hasNoChangeTracking: boolean, executeCommandStub: sinon.SinonStub ) => { - expect(executeCommandStub.getCall(1).args).to.eql([ + expect(executeCommandStub.getCall(2).args).to.eql([ 'setContext', 'sfdx:default_username_has_no_change_tracking', hasNoChangeTracking