Skip to content

Commit 57a0567

Browse files
committed
fix: add sandbox resume NUTs
1 parent a63efdb commit 57a0567

File tree

4 files changed

+214
-17
lines changed

4 files changed

+214
-17
lines changed

test/nut/sandboxCreate.nut.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('Sandbox Create', () => {
4949

5050
// add a sandbox definition file to the project
5151
const { SandboxName, LicenseType, Id } = getSandboxInfo();
52-
sandboxProcessSoql = getSandboxProcessSoql({ sandboxInfoId: Id });
52+
sandboxProcessSoql = getSandboxProcessSoql({ SandboxInfoId: Id });
5353
fs.writeFileSync(sandboxDefFilePath, JSON.stringify({ SandboxName, LicenseType }));
5454
});
5555

test/nut/sandboxRefresh.nut.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('Sandbox Refresh', () => {
9292
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
9393
const expectedCmdResponse = Object.assign({}, sbxProcess, { SandboxUsername: `${hubOrgUsername}.${sbxName}` });
9494
const sandboxInfoSoql = getSandboxInfoSoql(sbxName);
95-
const sandboxProcessSoql = getSandboxProcessSoql({ sandboxName: sbxName });
95+
const sandboxProcessSoql = getSandboxProcessSoql({ SandboxName: sbxName });
9696
const connection = await stubProdOrgConnection(sinonSandbox, hubOrgUsername);
9797

9898
const singleRecordQueryStub = stubSingleRecordQuery({ sinonSandbox, connection, sandboxInfoSoql, sbxInfo });
@@ -131,7 +131,7 @@ describe('Sandbox Refresh', () => {
131131
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
132132
const expectedCmdResponse = Object.assign({}, sbxProcess, { SandboxUsername: `${hubOrgUsername}.${sbxName}` });
133133
const sandboxInfoSoql = getSandboxInfoSoql(sbxName);
134-
const sandboxProcessSoql = getSandboxProcessSoql({ sandboxName: sbxName });
134+
const sandboxProcessSoql = getSandboxProcessSoql({ SandboxName: sbxName });
135135
const connection = await stubProdOrgConnection(sinonSandbox, hubOrgUsername);
136136

137137
const singleRecordQueryStub = stubSingleRecordQuery({ sinonSandbox, connection, sandboxInfoSoql, sbxInfo });
@@ -187,7 +187,7 @@ describe('Sandbox Refresh', () => {
187187
const sbxName = 'refrshSbx3';
188188
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
189189
const sandboxInfoSoql = getSandboxInfoSoql(sbxName);
190-
const sandboxProcessSoql = getSandboxProcessSoql({ sandboxName: sbxName });
190+
const sandboxProcessSoql = getSandboxProcessSoql({ SandboxName: sbxName });
191191
const connection = await stubProdOrgConnection(sinonSandbox, hubOrgUsername);
192192

193193
const noRecordsError = new Error('no SandboxInfo records');
@@ -220,7 +220,7 @@ describe('Sandbox Refresh', () => {
220220
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
221221
const expectedCmdResponse = Object.assign({}, sbxProcess, { SandboxUsername: `${hubOrgUsername}.${sbxName}` });
222222
const sandboxInfoSoql = getSandboxInfoSoql(sbxName);
223-
const sandboxProcessSoql = getSandboxProcessSoql({ sandboxName: sbxName });
223+
const sandboxProcessSoql = getSandboxProcessSoql({ SandboxName: sbxName });
224224

225225
const connection = await stubProdOrgConnection(sinonSandbox, hubOrgUsername);
226226

@@ -267,7 +267,7 @@ describe('Sandbox Refresh', () => {
267267
const sbxInfo = getSandboxInfo({ SandboxName: sbxName });
268268
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
269269
const sandboxInfoSoql = getSandboxInfoSoql(sbxName);
270-
const sandboxProcessSoql = getSandboxProcessSoql({ sandboxName: sbxName });
270+
const sandboxProcessSoql = getSandboxProcessSoql({ SandboxName: sbxName });
271271

272272
const updatedSbxProcess = getSandboxProcess({ Status: 'Processing', CopyProgress: 90, SandboxName: sbxName });
273273
const expectedCmdResponse = Object.assign({}, updatedSbxProcess, {
@@ -326,7 +326,7 @@ describe('Sandbox Refresh', () => {
326326
const sbxInfo = getSandboxInfo({ SandboxName: sbxName });
327327
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
328328
const sandboxInfoSoql = getSandboxInfoSoql(sbxName);
329-
const sandboxProcessSoql = getSandboxProcessSoql({ sandboxName: sbxName });
329+
const sandboxProcessSoql = getSandboxProcessSoql({ SandboxName: sbxName });
330330

331331
const completeSbxProcess = getSandboxProcess({
332332
Status: 'Completed',

test/nut/sandboxResume.nut.ts

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import fs from 'node:fs';
9+
import path from 'node:path';
10+
import { assert, expect } from 'chai';
11+
import sinon from 'sinon';
12+
import { TestSession, genUniqueString } from '@salesforce/cli-plugins-testkit';
13+
import { AuthInfo, Org, SandboxRequestCache } from '@salesforce/core';
14+
import { stubSfCommandUx, stubSpinner, stubUx } from '@salesforce/sf-plugins-core';
15+
import ResumeSandbox from '../../src/commands/org/resume/sandbox.js';
16+
import {
17+
deleteSandboxCacheFile,
18+
getSandboxInfo,
19+
getSandboxProcess,
20+
getSandboxProcessSoql,
21+
readAuthFile,
22+
readSandboxCacheFile,
23+
stubProdOrgConnection,
24+
stubToolingQuery,
25+
} from '../shared/sandboxMockUtils.js';
26+
27+
describe('Sandbox Resume', () => {
28+
let session: TestSession;
29+
let hubOrgUsername: string;
30+
let cacheFilePath: string;
31+
let sandboxDefFilePath: string;
32+
let sfCommandUxStubs: ReturnType<typeof stubSfCommandUx>;
33+
34+
const sinonSandbox = sinon.createSandbox();
35+
36+
before(async () => {
37+
const uid = genUniqueString('sbxResume_%s');
38+
session = await TestSession.create({
39+
project: { name: 'sandboxResume' },
40+
devhubAuthStrategy: 'AUTH_URL',
41+
sessionDir: path.join(process.cwd(), `test_session_${uid}`),
42+
});
43+
assert(session.hubOrg.username);
44+
hubOrgUsername = session.hubOrg.username;
45+
cacheFilePath = path.join(session.dir, '.sf', SandboxRequestCache.getFileName());
46+
sandboxDefFilePath = path.join(session.project.dir, 'sandboxDef.json');
47+
48+
// add a sandbox definition file to the project
49+
const { SandboxName, LicenseType } = getSandboxInfo();
50+
fs.writeFileSync(sandboxDefFilePath, JSON.stringify({ SandboxName, LicenseType }));
51+
});
52+
53+
beforeEach(() => {
54+
sfCommandUxStubs = stubSfCommandUx(sinonSandbox);
55+
stubUx(sinonSandbox);
56+
stubSpinner(sinonSandbox);
57+
});
58+
59+
after(async () => {
60+
await session?.clean();
61+
});
62+
63+
afterEach(() => {
64+
try {
65+
deleteSandboxCacheFile(cacheFilePath);
66+
} catch (err) {
67+
// ignore since there isn't always a cache file written
68+
}
69+
sinonSandbox.restore();
70+
});
71+
72+
//
73+
// NOTE: These tests use stubbed server responses since sandbox resume
74+
// takes a very long time.
75+
// Stubs:
76+
// 1. Connection.singleRecordQuery - used to query for the SandboxInfo
77+
// 2. Connection.tooling.update - used to update the SandboxInfo
78+
// 3. Connection.tooling.query - used to query for the SandboxProcess
79+
//
80+
81+
it('should return a SandboxProcessObject without polling using --name', async () => {
82+
const sbxName = 'resumeSbx1';
83+
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
84+
const sandboxProcessSoql = getSandboxProcessSoql({ SandboxName: sbxName });
85+
86+
const connection = await stubProdOrgConnection(sinonSandbox, hubOrgUsername);
87+
const toolingQueryStub = stubToolingQuery({ sinonSandbox, connection, sandboxProcessSoql, sbxProcess });
88+
89+
const result = await ResumeSandbox.run(['--name', sbxName, '-o', hubOrgUsername, '--json']);
90+
91+
expect(result).to.deep.equal(sbxProcess);
92+
expect(toolingQueryStub.calledOnce, 'toolingQueryStub').to.be.true;
93+
94+
// check the sandbox cache entry
95+
const cache = readSandboxCacheFile(cacheFilePath);
96+
expect(cache).to.have.property(sbxName);
97+
expect(cache[sbxName]).to.have.property('action', 'Create');
98+
expect(cache[sbxName]).to.have.property('prodOrgUsername', hubOrgUsername);
99+
expect(cache[sbxName]).to.have.deep.property('sandboxProcessObject', sbxProcess);
100+
expect(cache[sbxName]).to.have.deep.property('sandboxRequest', {});
101+
expect(sfCommandUxStubs).to.be.ok;
102+
});
103+
104+
it('should return a SandboxProcessObject without polling using --job-id', async () => {
105+
const sbxName = 'resumeSbx2';
106+
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
107+
const sandboxProcessSoql = getSandboxProcessSoql({ Id: sbxProcess.Id });
108+
109+
const connection = await stubProdOrgConnection(sinonSandbox, hubOrgUsername);
110+
const toolingQueryStub = stubToolingQuery({ sinonSandbox, connection, sandboxProcessSoql, sbxProcess });
111+
112+
const result = await ResumeSandbox.run(['--job-id', sbxProcess.Id, '-o', hubOrgUsername, '--json']);
113+
114+
expect(result).to.deep.equal(sbxProcess);
115+
expect(toolingQueryStub.calledOnce, 'toolingQueryStub').to.be.true;
116+
117+
// check the sandbox cache entry
118+
const cache = readSandboxCacheFile(cacheFilePath);
119+
expect(cache).to.have.property(sbxName);
120+
expect(cache[sbxName]).to.have.property('action', 'Create');
121+
expect(cache[sbxName]).to.have.property('prodOrgUsername', hubOrgUsername);
122+
expect(cache[sbxName]).to.have.deep.property('sandboxProcessObject', sbxProcess);
123+
expect(cache[sbxName]).to.have.deep.property('sandboxRequest', {});
124+
expect(sfCommandUxStubs).to.be.ok;
125+
});
126+
127+
it('should return a complete SandboxProcessObject when polling using --job-id', async () => {
128+
const sbxName = 'resumeSbx3';
129+
const sbxProcess = getSandboxProcess({ SandboxName: sbxName });
130+
const completeSbxProcess = getSandboxProcess({
131+
SandboxName: sbxName,
132+
Status: 'Completed',
133+
CopyProgress: 100,
134+
EndDate: '2024-02-22T00:37:46.000+0000',
135+
});
136+
const expectedCmdResponse = Object.assign({}, completeSbxProcess, {
137+
SandboxUsername: `${hubOrgUsername}.${sbxName}`,
138+
});
139+
// const sandboxProcessSoql = getSandboxProcessSoql({ Id: sbxProcess.Id });
140+
141+
// const connection = await stubProdOrgConnection(sinonSandbox, hubOrgUsername);
142+
// const toolingQueryStub = stubToolingQuery({ sinonSandbox, connection, sandboxProcessSoql, sbxProcess });
143+
144+
// This call is used in polling; Org.pollStatusAndAuth()
145+
const querySandboxProcessByIdStub = sinonSandbox
146+
.stub(Org.prototype, 'querySandboxProcessById')
147+
.withArgs(sbxProcess.Id)
148+
.resolves(completeSbxProcess);
149+
150+
const sbxAuthResponse = {
151+
authUserName: `${hubOrgUsername}.${sbxName}`,
152+
authCode: '1qa2ws3ed4rf',
153+
instanceUrl: `https://sf0927--${sbxName}.sandbox.my.salesforce.com`,
154+
loginUrl: 'https://test.salesforce.com',
155+
};
156+
// This call is to auth a completed sandbox refresh
157+
const sandboxSignupCompleteStub = sinonSandbox
158+
// @ts-expect-error stubbing private function
159+
.stub(Org.prototype, 'sandboxSignupComplete')
160+
.resolves(sbxAuthResponse);
161+
162+
// Stub AuthInfo functions so an auth file is written without making http calls
163+
// @ts-expect-error stubbing private function
164+
const authInfoExchangeTokenStub = sinonSandbox.stub(AuthInfo.prototype, 'exchangeToken').resolves({
165+
username: sbxAuthResponse.authUserName,
166+
parentUsername: hubOrgUsername,
167+
instanceUrl: sbxAuthResponse.instanceUrl,
168+
loginUrl: sbxAuthResponse.loginUrl,
169+
accessToken: 'fake-access-token-1234',
170+
orgId: sbxProcess.SandboxOrganization,
171+
});
172+
// @ts-expect-error stubbing private function
173+
sinonSandbox.stub(AuthInfo.prototype, 'determineIfDevHub').resolves(false);
174+
// @ts-expect-error stubbing private function
175+
sinonSandbox.stub(AuthInfo.prototype, 'getNamespacePrefix').resolves();
176+
177+
const result = await ResumeSandbox.run(['--job-id', sbxProcess.Id, '-o', hubOrgUsername, '--wait', '1', '--json']);
178+
179+
// result will be the last SandboxProcess
180+
expect(result, 'checking result').to.deep.equal(expectedCmdResponse);
181+
// expect(toolingQueryStub.calledOnce, 'toolingQueryStub called').to.be.true;
182+
expect(querySandboxProcessByIdStub.called, 'querySandboxProcessByIdStub called').to.be.true;
183+
expect(sandboxSignupCompleteStub.called, 'sandboxSignupCompleteStub called').to.be.true;
184+
expect(authInfoExchangeTokenStub.called, 'authInfoExchangeTokenStub called').to.be.true;
185+
186+
// Check auth files exist
187+
const authFileContents = readAuthFile(session.homeDir, sbxAuthResponse.authUserName);
188+
expect(authFileContents).to.be.ok;
189+
expect(authFileContents).to.have.property('username', sbxAuthResponse.authUserName);
190+
expect(authFileContents).to.have.property('parentUsername', hubOrgUsername);
191+
expect(authFileContents).to.have.property('accessToken');
192+
expect(authFileContents).to.have.property('isSandbox', true);
193+
expect(authFileContents).to.not.have.property('authCode');
194+
195+
// check sandbox auth file doesn't exist
196+
try {
197+
readSandboxCacheFile(cacheFilePath);
198+
assert(false, 'should not have found a sandbox cache file');
199+
} catch (err) {
200+
// ignore
201+
}
202+
});
203+
});

test/shared/sandboxMockUtils.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,8 @@ export const getSandboxProcess = (overrides?: Partial<SandboxProcessObject>): Sa
8888
export const getSandboxInfoSoql = (sandboxName = sbxName) =>
8989
`SELECT ${sandboxInfoFields.join(',')} FROM SandboxInfo WHERE SandboxName='${sandboxName}'`;
9090
export const getSandboxProcessSoql = (cfg?: SbxProcessSqlConfig) => {
91-
const whereClause = cfg?.sandboxName
92-
? `SandboxName='${cfg.sandboxName}'`
93-
: cfg?.sandboxInfoId
94-
? `SandboxInfoId='${cfg.sandboxInfoId}'`
95-
: `SandboxName='${sbxName}'`;
96-
91+
const entries = cfg ? Object.entries(cfg)[0] : ['SandboxName', sbxName];
92+
const whereClause = `${entries[0]}='${entries[1]}'`;
9793
return `SELECT ${sandboxProcessFields.join(',')} FROM SandboxProcess WHERE ${whereClause} ORDER BY CreatedDate DESC`;
9894
};
9995

@@ -182,10 +178,8 @@ type ToolingQueryStubConfig = {
182178
sbxProcess: SandboxProcessObject;
183179
};
184180

185-
type SbxProcessSqlConfig = {
186-
sandboxName?: string;
187-
sandboxInfoId?: string;
188-
};
181+
type SbxProcessSqlConfig = { SandboxName: string } | { SandboxInfoId: string } | { Id: string };
182+
189183
/**
190184
* Stubs the query for the newly created SandboxProcess from a sandbox update.
191185
* Stubs the query for a cloned sandbox process (see `CreateSandbox.getSourceId()`).

0 commit comments

Comments
 (0)