Skip to content

Commit

Permalink
Merge pull request #164 from forcedotcom/tnoonan/develop
Browse files Browse the repository at this point in the history
fix: deleting and org auth file never worked
  • Loading branch information
tnoonan-salesforce authored Aug 13, 2019
2 parents 44424d8 + 7c1f630 commit 8c77571
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 51 deletions.
146 changes: 99 additions & 47 deletions src/org.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { QueryResult } from 'jsforce';
import { join as pathJoin } from 'path';
import { AuthFields, AuthInfo } from './authInfo';
import { Aliases } from './config/aliases';
import { AuthInfoConfig } from './config/authInfoConfig';
import { Config } from './config/config';
import { ConfigAggregator, ConfigInfo } from './config/configAggregator';
import { ConfigContents } from './config/configStore';
Expand Down Expand Up @@ -127,53 +128,14 @@ export class Org extends AsyncCreatable<Org.Options> {
if (this.getConnection().isUsingAccessToken()) {
return Promise.resolve();
}

const auths: AuthInfo[] = await this.readUserAuthFiles();
const aliases: Aliases = await Aliases.create(Aliases.getDefaultOptions());
this.logger.info(`Cleaning up usernames in org: ${this.getOrgId()}`);

for (const auth of auths) {
const username = auth.getFields().username;

const aliasKeys = (username && aliases.getKeysByValue(username)) || [];
aliases.unsetAll(aliasKeys);

let orgForUser;
if (username === this.getUsername()) {
orgForUser = this;
} else {
const _info = await AuthInfo.create({ username });
const connection: Connection = await Connection.create({ authInfo: _info });
orgForUser = await Org.create({ connection });
}

const orgType = this.isDevHubOrg() ? Config.DEFAULT_DEV_HUB_USERNAME : Config.DEFAULT_USERNAME;

const configInfo: ConfigInfo = await orgForUser.configAggregator.getInfo(orgType);

if (
(configInfo.value === username || aliasKeys.includes(configInfo.value as string)) &&
(configInfo.isGlobal() || configInfo.isLocal())
) {
await Config.update(configInfo.isGlobal(), orgType, undefined);
}

const orgUsers: OrgUsersConfig = await this.retrieveOrgUsersConfig();
await this.manageDelete(async () => await orgUsers.unlink(), orgUsers.getPath(), throwWhenRemoveFails);
}

await aliases.write();

// Delete the sandbox org config file if it exists.
// This is an optional file so don't throw an error when the file doesn't exist.
const sandboxOrgConfig = await this.retrieveSandboxOrgConfig();
if (await sandboxOrgConfig.exists()) {
await this.manageDelete(
async () => await sandboxOrgConfig.unlink(),
sandboxOrgConfig.getPath(),
throwWhenRemoveFails
);
}
await this.removeSandboxConfig(throwWhenRemoveFails);
await this.removeUsers(throwWhenRemoveFails);
await this.removeUsersConfig();
// An attempt to remove this org's auth file occurs in this.removeUsersConfig. That's because this org's usersname is also
// included in the OrgUser config file.
//
// So, just in case no users are added to this org we will try the remove again.
await this.removeAuth();
}

/**
Expand Down Expand Up @@ -523,6 +485,36 @@ export class Org extends AsyncCreatable<Org.Options> {
throw new SfdxError('Not Supported');
}

/**
* Returns a promise to delete an auth info file from the local file system and any related cache information for
* this Org.. You don't want to call this method directly. Instead consider calling Org.remove()
*/
private async removeAuth(): Promise<void> {
const username = ensure(this.getUsername());
this.logger.debug(`Removing auth for user: ${username}`);
const config = await AuthInfoConfig.create({
...AuthInfoConfig.getOptions(username),
throwOnNotFound: false
});

this.logger.debug(`Clearing auth cache for user: ${username}`);
AuthInfo.clearCache(username);
if (await config.exists()) {
await config.unlink();
}
}

/**
* Deletes the users config file
*/
private async removeUsersConfig() {
const config = await this.retrieveOrgUsersConfig();
if (await config.exists()) {
this.logger.debug(`Removing org users config at: ${config.getPath()}`);
await config.unlink();
}
}

/**
* @ignore
*/
Expand All @@ -540,6 +532,66 @@ export class Org extends AsyncCreatable<Org.Options> {
}
});
}

/**
* Remove the org users auth file.
* @param throwWhenRemoveFails true if manageDelete should throw or not if the deleted fails.
*/
private async removeUsers(throwWhenRemoveFails: boolean) {
this.logger.debug(`Removing users associate with org: ${this.getOrgId()}`);
const config = await this.retrieveOrgUsersConfig();
this.logger.debug(`using path for org users: ${config.getPath()}`);
if (await config.exists()) {
const _auths: AuthInfo[] = await this.readUserAuthFiles();
const aliases: Aliases = await Aliases.create(Aliases.getDefaultOptions());
this.logger.info(`Cleaning up usernames in org: ${this.getOrgId()}`);

for (const auth of _auths) {
const username = auth.getFields().username;

const aliasKeys = (username && aliases.getKeysByValue(username)) || [];
aliases.unsetAll(aliasKeys);

let orgForUser;
if (username === this.getUsername()) {
orgForUser = this;
} else {
const _info = await AuthInfo.create({ username });
const connection: Connection = await Connection.create({ authInfo: _info });
orgForUser = await Org.create({ connection });
}

const orgType = this.isDevHubOrg() ? Config.DEFAULT_DEV_HUB_USERNAME : Config.DEFAULT_USERNAME;

const configInfo: ConfigInfo = await orgForUser.configAggregator.getInfo(orgType);

if (
(configInfo.value === username || aliasKeys.includes(configInfo.value as string)) &&
(configInfo.isGlobal() || configInfo.isLocal())
) {
await Config.update(configInfo.isGlobal(), orgType, undefined);
}
await orgForUser.removeAuth();
}

await aliases.write();
}
}

/**
* Remove an associate sandbox config.
* @param throwWhenRemoveFails true if manageDelete should throw or not if the deleted fails.
*/
private async removeSandboxConfig(throwWhenRemoveFails: boolean) {
const sandboxOrgConfig = await this.retrieveSandboxOrgConfig();
if (await sandboxOrgConfig.exists()) {
await this.manageDelete(
async () => await sandboxOrgConfig.unlink(),
sandboxOrgConfig.getPath(),
throwWhenRemoveFails
);
}
}
}

export namespace Org {
Expand Down
64 changes: 60 additions & 4 deletions test/unit/orgTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,14 @@ describe('Org Tests', () => {
});

it('should remove config setting', async () => {
stubMethod($$.SANDBOX, ConfigFile.prototype, 'exists').callsFake(async function() {
return this.path && this.path.endsWith(`${testData.orgId}.json`);
});

stubMethod($$.SANDBOX, fs, 'unlink').callsFake(() => {
return Promise.resolve({});
});

const configAggregator: ConfigAggregator = await ConfigAggregator.create();
const org: Org = await Org.create({
connection: await Connection.create({
Expand All @@ -301,6 +309,13 @@ describe('Org Tests', () => {
});

it('should remove the alias', async () => {
stubMethod($$.SANDBOX, ConfigFile.prototype, 'exists').callsFake(async function() {
return this.path && this.path.endsWith(`${testData.orgId}.json`);
});

stubMethod($$.SANDBOX, fs, 'unlink').callsFake(() => {
return Promise.resolve({});
});
const org: Org = await Org.create({
connection: await Connection.create({
authInfo: await AuthInfo.create({ username: testData.username })
Expand Down Expand Up @@ -336,10 +351,6 @@ describe('Org Tests', () => {

await org.remove();

expect(deletedPaths).includes(
pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${testData.orgId}.json`)
);

expect(deletedPaths).not.includes(
pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${testData.orgId}.sandbox.json`)
);
Expand Down Expand Up @@ -430,6 +441,8 @@ describe('Org Tests', () => {
await config.set(Config.DEFAULT_USERNAME, ensureString(org0Username));
await config.write();

expect(await config.exists()).to.be.true;

const configAggregator = await orgs[0].getConfigAggregator().reload();
const info = configAggregator.getInfo(Config.DEFAULT_USERNAME);
expect(info).has.property('value', org0Username);
Expand All @@ -446,6 +459,12 @@ describe('Org Tests', () => {

alias = await Aliases.fetch('foo');
expect(alias).eq(undefined);

const configOrg0 = await AuthInfoConfig.create({
...AuthInfoConfig.getOptions(orgs[0].getUsername()),
throwOnNotFound: false
});
expect(await configOrg0.exists()).to.be.false;
});

it('should not try to delete auth files when deleting an org via access token', async () => {
Expand Down Expand Up @@ -717,5 +736,42 @@ describe('Org Tests', () => {

expect(await org.getSandboxOrgConfigField(SandboxOrgConfig.Fields.PROD_ORG_USERNAME)).to.eq('user@sandbox.org');
});

it('Test sandbox config removal.', async () => {
// Stub exists so only the auth file and sandbox config file exist. No users config file.
stubMethod($$.SANDBOX, ConfigFile.prototype, 'exists').callsFake(async function() {
if (this.path && this.path.endsWith(`${testData.orgId}.json`)) {
return Promise.resolve(false);
}
return Promise.resolve(true);
});

// Stub to track the deleted paths.
const deletedPaths: string[] = [];
stubMethod($$.SANDBOX, ConfigFile.prototype, 'unlink').callsFake(function(this: ConfigFile<ConfigFile.Options>) {
deletedPaths.push(this.getPath());
return Promise.resolve({});
});

// Create an org and add a sandbox config
const org: Org = await Org.create({ aliasOrUsername: testData.username });
expect(await org.getSandboxOrgConfigField(SandboxOrgConfig.Fields.PROD_ORG_USERNAME)).to.be.undefined;
await org.setSandboxOrgConfigField(SandboxOrgConfig.Fields.PROD_ORG_USERNAME, 'user@sandbox.org');
expect(await org.getSandboxOrgConfigField(SandboxOrgConfig.Fields.PROD_ORG_USERNAME)).to.eq('user@sandbox.org');

// Remove the org
await org.remove();

// Expect there are only two files.
expect(deletedPaths).to.have.length(2);
// Expect the sandbox config is deleted.
expect(deletedPaths).includes(
pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${testData.orgId}.sandbox.json`)
);
// Expect the auth file is deleted.
expect(deletedPaths).includes(
pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${org.getUsername()}.json`)
);
});
});
});

0 comments on commit 8c77571

Please sign in to comment.