Skip to content

Commit ca6fc5f

Browse files
Merge pull request #806 from salesforcecli/er/nutsForLogicCommands
W-19625073: add NUTs for logic commands
2 parents 0d725dd + f42d603 commit ca6fc5f

File tree

7 files changed

+476
-0
lines changed

7 files changed

+476
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2025, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
17+
import { expect, config } from 'chai';
18+
import { TestRunIdResult } from '@salesforce/apex-node/lib/src/tests/types.js';
19+
import { RunResult } from '../../../../src/reporters/index.js';
20+
import { setupUnifiedFrameworkProject } from '../testHelper.js';
21+
22+
config.truncateThreshold = 0;
23+
24+
describe('logic get test', () => {
25+
let session: TestSession;
26+
let testRunId: string | undefined;
27+
28+
before(async () => {
29+
session = await setupUnifiedFrameworkProject();
30+
31+
32+
// Run tests to get a test run ID for subsequent get operations
33+
testRunId = execCmd<TestRunIdResult>('logic:run:test --test-level RunLocalTests --json', {
34+
ensureExitCode: 0,
35+
}).jsonOutput?.result.testRunId.trim();
36+
37+
expect(testRunId).to.be.a('string');
38+
expect(testRunId).to.match(/^707/); // Test run IDs start with 707
39+
});
40+
41+
after(async () => {
42+
await session?.clean();
43+
});
44+
45+
describe('basic functionality', () => {
46+
it('should get test results with default format', async () => {
47+
const result = execCmd(`logic:get:test --test-run-id ${testRunId}`, { ensureExitCode: 0 });
48+
const output = result.shellOutput.stdout;
49+
50+
// Verify basic output structure
51+
expect(output).to.include('Test Results');
52+
expect(output).to.include('Outcome');
53+
expect(output).to.include('CATEGORY');
54+
expect(output).to.include('Apex');
55+
expect(output).to.include('Flow');
56+
});
57+
58+
it('should get test results in JSON format', async () => {
59+
const result = execCmd<RunResult>(`logic:get:test --test-run-id ${testRunId} --json`, {
60+
ensureExitCode: 0
61+
});
62+
63+
const jsonResult = result.jsonOutput?.result;
64+
expect(jsonResult).to.be.an('object');
65+
expect(jsonResult).to.have.property('summary');
66+
expect(jsonResult?.summary).to.have.property('outcome');
67+
expect(jsonResult?.summary).to.have.property('testsRan');
68+
expect(jsonResult?.tests.length).to.be.greaterThan(0);
69+
jsonResult!.tests.forEach((test) => {
70+
expect(test).to.have.property('Category');
71+
});
72+
});
73+
74+
it('should get test results with code coverage', async () => {
75+
const result = execCmd(`logic:get:test --test-run-id ${testRunId} --code-coverage`, {
76+
ensureExitCode: 0
77+
});
78+
const output = result.shellOutput.stdout;
79+
// Verify code coverage information is included
80+
expect(output).to.include('Code Coverage');
81+
});
82+
});
83+
84+
describe('result formats', () => {
85+
it('should output in JSON format', async () => {
86+
const result = execCmd(`logic:get:test --test-run-id ${testRunId} --result-format json`, {
87+
ensureExitCode: 0
88+
});
89+
const output = result.shellOutput.stdout;
90+
91+
// Should be valid JSON
92+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment
93+
expect(() => JSON.parse(output)).to.not.throw();
94+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
95+
const jsonOutput = JSON.parse(output);
96+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
97+
expect(jsonOutput?.result).to.have.property('summary');
98+
});
99+
});
100+
101+
describe('code coverage options', () => {
102+
103+
it('should include code coverage in JSON format', async () => {
104+
const result = execCmd<RunResult>(`logic:get:test --test-run-id ${testRunId} --code-coverage --json`, {
105+
ensureExitCode: 0
106+
});
107+
108+
const jsonResult = result.jsonOutput?.result;
109+
expect(jsonResult).to.have.property('coverage');
110+
});
111+
});
112+
});
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2025, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
17+
import { expect, config } from 'chai';
18+
import { TestRunIdResult } from '@salesforce/apex-node/lib/src/tests/types.js';
19+
import { RunResult } from '../../../../src/reporters/index.js';
20+
import { setupUnifiedFrameworkProject } from '../testHelper.js';
21+
22+
config.truncateThreshold = 0;
23+
24+
describe('logic run test', () => {
25+
let session: TestSession;
26+
before(async () => {
27+
session = await setupUnifiedFrameworkProject();
28+
});
29+
30+
after(async () => {
31+
await session?.clean();
32+
});
33+
34+
describe('--test-category', () => {
35+
it('will run tests with Apex category', async () => {
36+
const result = execCmd('logic:run:test --test-category Apex --test-level RunLocalTests --wait 10', {
37+
ensureExitCode: 0
38+
}).shellOutput.stdout;
39+
expect(result).to.include('CATEGORY');
40+
expect(result).to.include('Apex');
41+
expect(result).to.not.include('Flow');
42+
});
43+
44+
it('will run tests with Flow category', async () => {
45+
const result = execCmd('logic:run:test --test-category Flow --test-level RunLocalTests --wait 10', {
46+
ensureExitCode: 0
47+
}).shellOutput.stdout;
48+
expect(result).to.include('CATEGORY');
49+
expect(result).to.include('Flow');
50+
expect(result).to.not.include('Apex');
51+
});
52+
53+
it('will run tests with multiple categories', async () => {
54+
const result = execCmd('logic:run:test --test-category Apex --test-category Flow --test-level RunLocalTests --wait 10', {
55+
ensureExitCode: 0
56+
}).shellOutput.stdout;
57+
expect(result).to.include('CATEGORY');
58+
expect(result).to.include('Apex');
59+
expect(result).to.include('Flow');
60+
});
61+
});
62+
63+
describe('--class-names', () => {
64+
it('will run specified class', async () => {
65+
const result = execCmd('logic:run:test --class-names GeocodingServiceTest --wait 10', {
66+
ensureExitCode: 0
67+
}).shellOutput.stdout;
68+
expect(result).to.match(/Tests Ran\s+3/);
69+
expect(result).to.include('GeocodingServiceTest');
70+
expect(result).to.include('CATEGORY');
71+
expect(result).to.include('Apex');
72+
expect(result).to.not.include('Flow');
73+
});
74+
75+
it('will run multiple specified classes from different categories', async () => {
76+
const result = execCmd('logic:run:test --class-names GeocodingServiceTest --class-names FlowTesting.Populate_opp_description --wait 10', {
77+
ensureExitCode: 0
78+
}).shellOutput.stdout;
79+
expect(result).to.match(/Tests Ran\s+4/);
80+
expect(result).to.include('CATEGORY');
81+
expect(result).to.include('Apex');
82+
expect(result).to.include('Flow');
83+
});
84+
});
85+
86+
describe('--tests', () => {
87+
it('will run specified test methods', async () => {
88+
const result = execCmd('logic:run:test --tests FlowTesting.Populate_opp_description.test_opportunity_updates --wait 10', {
89+
ensureExitCode: 0
90+
}).shellOutput.stdout;
91+
expect(result).to.match(/Tests Ran\s+1/);
92+
expect(result).to.include('CATEGORY');
93+
expect(result).to.include('Flow');
94+
expect(result).to.include('Populate_opp_description.test_opportunity_updates');
95+
expect(result).to.not.include('Apex');
96+
});
97+
98+
it('will run multiple test methods from different categories', async () => {
99+
const result = execCmd('logic:run:test --tests TestPropertyController.testGetPicturesWithResults --tests FlowTesting.Populate_opp_description.test_opportunity_updates --wait 10', {
100+
ensureExitCode: 0
101+
}).shellOutput.stdout;
102+
expect(result).to.match(/Tests Ran\s+2/);
103+
expect(result).to.include('CATEGORY');
104+
expect(result).to.include('Apex');
105+
expect(result).to.include('Flow');
106+
expect(result).to.include('TestPropertyController.testGetPicturesWithResults');
107+
expect(result).to.include('Populate_opp_description.test_opportunity_updates');
108+
});
109+
});
110+
111+
describe('JSON output', () => {
112+
it('will run tests and return JSON with Category property', async () => {
113+
const result = execCmd<RunResult>('logic:run:test --test-level RunLocalTests --wait 10 --json', {
114+
ensureExitCode: 0
115+
}).jsonOutput?.result;
116+
expect(result?.tests.length).to.be.greaterThan(0);
117+
expect(result?.summary.outcome).to.equal('Passed');
118+
expect(result?.summary).to.have.all.keys(
119+
'outcome',
120+
'testsRan',
121+
'passing',
122+
'failing',
123+
'skipped',
124+
'passRate',
125+
'failRate',
126+
'testStartTime',
127+
'testExecutionTime',
128+
'testTotalTime',
129+
'commandTime',
130+
'hostname',
131+
'orgId',
132+
'username',
133+
'testRunId',
134+
'userId'
135+
);
136+
expect(result?.tests[0]).to.have.all.keys(
137+
'Id',
138+
'QueueItemId',
139+
'StackTrace',
140+
'Message',
141+
'AsyncApexJobId',
142+
'Category',
143+
'MethodName',
144+
'Outcome',
145+
'ApexClass',
146+
'RunTime',
147+
'FullName'
148+
);
149+
});
150+
});
151+
152+
describe('async execution', () => {
153+
it('will run tests async and return test run id', async () => {
154+
const result = execCmd<TestRunIdResult>('logic:run:test --test-category Flow --test-level RunLocalTests --json', {
155+
ensureExitCode: 0
156+
}).jsonOutput?.result;
157+
expect(result?.testRunId).to.be.a('string');
158+
expect(result?.testRunId.startsWith('707')).to.be.true;
159+
});
160+
});
161+
162+
describe('--synchronous', () => {
163+
it('will run tests synchronously', async () => {
164+
const result = execCmd('logic:run:test --test-category Apex --test-level RunLocalTests --synchronous', {
165+
ensureExitCode: 0
166+
}).shellOutput.stdout;
167+
expect(result).to.include('Outcome');
168+
expect(result).to.include('CATEGORY');
169+
expect(result).to.include('Apex');
170+
expect(result).to.include('Passed');
171+
});
172+
});
173+
});

test/commands/logic/testHelper.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2025, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import path from 'node:path';
17+
import fs from 'node:fs';
18+
import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
19+
20+
/**
21+
* Helper method to add flow and flow test to the project.
22+
* Also updates the sourceApiVersion to 65.0
23+
*/
24+
export async function setupUnifiedFrameworkProject(): Promise<TestSession> {
25+
// eslint-disable-next-line no-param-reassign
26+
const session = await TestSession.create({
27+
project: {
28+
gitClone: 'https://github.com/trailheadapps/dreamhouse-lwc.git',
29+
},
30+
devhubAuthStrategy: 'AUTO',
31+
scratchOrgs: [
32+
{
33+
config: path.resolve('test', 'nuts', 'unifiedFrameworkProject', 'config', 'project-scratch-def.json'),
34+
setDefault: true,
35+
alias: 'org',
36+
},
37+
],
38+
});
39+
const flowXml = path.join('test', 'nuts', 'unifiedFrameworkProject', 'force-app', 'main', 'default', 'flows', 'Populate_opp_description.flow-meta.xml');
40+
const flowsDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'flows');
41+
const targetFile = path.join(flowsDir, 'Populate_opp_description.flow-meta.xml');
42+
43+
if (!fs.existsSync(flowsDir)) {
44+
fs.mkdirSync(flowsDir, { recursive: true });
45+
}
46+
47+
fs.copyFileSync(flowXml, targetFile);
48+
49+
const flowTestXml = path.join('test', 'nuts', 'unifiedFrameworkProject', 'force-app', 'main', 'default', 'flowtests', 'test_opportunity_updates.flowtest-meta.xml');
50+
const flowTestsDir = path.join(session.project.dir, 'force-app', 'main', 'default', 'flowtests');
51+
const targetTestFile = path.join(flowTestsDir, 'test_opportunity_updates.flowtest-meta.xml');
52+
53+
if (!fs.existsSync(flowTestsDir)) {
54+
fs.mkdirSync(flowTestsDir, { recursive: true });
55+
}
56+
57+
fs.copyFileSync(flowTestXml, targetTestFile);
58+
59+
const sfdxProjectPath = path.join(session.project.dir, 'sfdx-project.json');
60+
61+
// We need to update the sourceApiVersion to 65.0 because the changes in the api are not supported in 64.0
62+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
63+
const sfdxProject = JSON.parse(fs.readFileSync(sfdxProjectPath, 'utf8'));
64+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
65+
sfdxProject.sourceApiVersion = parseInt(sfdxProject.sourceApiVersion, 10) < 65 ? '65.0' : sfdxProject.sourceApiVersion;
66+
fs.writeFileSync(sfdxProjectPath, JSON.stringify(sfdxProject, null, 2));
67+
68+
execCmd('project:deploy:start --source-dir force-app', { ensureExitCode: 0, cli: 'sf' });
69+
return session;
70+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"orgName": "Easy Spaces",
3+
"edition": "Developer",
4+
"release": "preview",
5+
"hasSampleData": false,
6+
"features": ["Walkthroughs", "EnableSetPasswordInApi"],
7+
"settings": {
8+
"lightningExperienceSettings": {
9+
"enableS1DesktopEnabled": true
10+
},
11+
"mobileSettings": {
12+
"enableS1EncryptedStoragePref2": false
13+
},
14+
"pathAssistantSettings": {
15+
"pathAssistantEnabled": true
16+
},
17+
"userEngagementSettings": {
18+
"enableOrchestrationInSandbox": true,
19+
"enableShowSalesforceUserAssist": false
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)