Skip to content

Commit 6e74824

Browse files
committed
test: nuts to validate forceignore behavior
1 parent a1e1dd7 commit 6e74824

File tree

8 files changed

+140
-29
lines changed

8 files changed

+140
-29
lines changed

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"test:nuts:commands:conflicts": "nyc mocha \"**/commands/conflicts.nut.ts\" --slow 4500 --timeout 600000",
2929
"test:nuts:commands:remote": "nyc mocha \"**/commands/remoteChanges.nut.ts\" --slow 4500 --timeout 600000",
3030
"test:nuts:commands:reset-clear": "nyc mocha \"**/commands/resetClear.nut.ts\" --slow 4500 --timeout 600000",
31+
"test:nuts:commands:ignore": "nyc mocha \"**/commands/forceIgnore.nut.ts\" --slow 4500 --timeout 600000",
3132
"prune:dead": "ts-prune | grep -v 'src/commands/' | grep -v 'source-deploy-retrieve'"
3233
},
3334
"keywords": [
@@ -69,6 +70,7 @@
6970
"@salesforce/plugin-command-reference": "^1.3.4",
7071
"@salesforce/prettier-config": "^0.0.2",
7172
"@salesforce/ts-sinon": "^1.3.21",
73+
"@types/shelljs": "^0.8.9",
7274
"@types/sinon": "^10.0.2",
7375
"@typescript-eslint/eslint-plugin": "^4.29.0",
7476
"@typescript-eslint/parser": "^4.29.0",
@@ -88,6 +90,7 @@
8890
"nyc": "^15.1.0",
8991
"prettier": "^2.3.2",
9092
"pretty-quick": "^3.1.1",
93+
"shelljs": "^0.8.4",
9194
"sinon": "^10.0.0",
9295
"ts-node": "^10.1.0",
9396
"ts-prune": "^0.10.0",

src/shared/localShadowRepo.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
/* eslint-disable no-console */
88

99
import { join as pathJoin } from 'path';
10-
import { promises as fs, existsSync } from 'fs';
10+
// what's fsp? well, we'd like to fs.promises but can't until we upgrade node beyond 12
11+
import { promises as fsp, existsSync } from 'fs';
12+
import * as fs from 'fs';
1113
import { AsyncCreatable } from '@salesforce/kit';
1214
import { NamedPackageDir, Logger } from '@salesforce/core';
1315
import * as git from 'isomorphic-git';
@@ -74,12 +76,12 @@ export class ShadowRepo extends AsyncCreatable<ShadowRepoOptions> {
7476
*
7577
*/
7678
public async gitInit(): Promise<void> {
77-
await fs.mkdir(this.gitDir, { recursive: true });
79+
await fsp.mkdir(this.gitDir, { recursive: true });
7880
await git.init({ fs, dir: this.projectPath, gitdir: this.gitDir, defaultBranch: 'main' });
7981
}
8082

8183
public async delete(): Promise<string> {
82-
await fs.rm(this.gitDir, { recursive: true, force: true });
84+
await fsp.rm(this.gitDir, { recursive: true, force: true });
8385
return this.gitDir;
8486
}
8587
/**
@@ -209,14 +211,14 @@ export class ShadowRepo extends AsyncCreatable<ShadowRepoOptions> {
209211
private async stashIgnoreFile(): Promise<void> {
210212
if (!this.stashed) {
211213
this.stashed = true;
212-
await fs.rename(pathJoin(this.projectPath, '.gitignore'), pathJoin(this.projectPath, '.BAK.gitignore'));
214+
await fsp.rename(pathJoin(this.projectPath, '.gitignore'), pathJoin(this.projectPath, '.BAK.gitignore'));
213215
}
214216
}
215217

216218
private async unStashIgnoreFile(): Promise<void> {
217219
if (this.stashed) {
218220
this.stashed = false;
219-
await fs.rename(pathJoin(this.projectPath, '.BAK.gitignore'), pathJoin(this.projectPath, '.gitignore'));
221+
await fsp.rename(pathJoin(this.projectPath, '.BAK.gitignore'), pathJoin(this.projectPath, '.gitignore'));
220222
}
221223
}
222224
}

src/sourceTracking.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ export class SourceTracking extends AsyncCreatable {
143143
.filter(sourceComponentGuard)
144144
.map((component) => componentSet.add(component, true));
145145

146-
// make SourceComponents from deletes and add to toDeploy
146+
// there might have been components in local tracking, but they might be ignored by SDR or unresolvable.
147+
// SDR will throw when you try to resolve them, so don't
148+
if (componentSet.size === 0) {
149+
return [];
150+
}
147151
const deploy = await componentSet.deploy({ usernameOrConnection: this.username, apiOptions: { ignoreWarnings } });
148152
const result = await deploy.pollStatus(30, wait.seconds);
149153

test/nuts/commands/conflicts.nut.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
import * as path from 'path';
1313
import { promises as fs } from 'fs';
14+
import { expect } from 'chai';
1415

1516
import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit';
1617
import { Connection, AuthInfo } from '@salesforce/core';
17-
import { expect } from 'chai';
1818
import { StatusResult } from '../../../src/commands/force/source/status';
1919

2020
let session: TestSession;
@@ -39,7 +39,9 @@ describe('conflict detection and resolution', () => {
3939
});
4040
it('edits a remote file', async () => {
4141
const conn = await Connection.create({
42-
authInfo: await AuthInfo.create({ username: session.setup[0].result?.username as string }),
42+
authInfo: await AuthInfo.create({
43+
username: (session.setup[0] as { result: { username: string } }).result?.username,
44+
}),
4345
});
4446
const app = await conn.singleRecordQuery<{ Id: string; Metadata: any }>(
4547
"select Id, Metadata from CustomApplication where DeveloperName = 'EBikes'",

test/nuts/commands/forceIgnore.nut.ts

+105-17
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,117 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
9+
/* eslint-disable no-console */
10+
/* eslint-disable @typescript-eslint/no-explicit-any */
11+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
12+
13+
import * as path from 'path';
14+
import { promises as fs } from 'fs';
15+
import { expect } from 'chai';
16+
import * as shell from 'shelljs';
17+
18+
import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit';
19+
import { Connection, AuthInfo } from '@salesforce/core';
20+
import { PushPullResponse } from '../../../src/shared/types';
21+
import { StatusResult } from '../../../src/commands/force/source/status';
22+
23+
let session: TestSession;
24+
const classdir = 'force-app/main/default/classes';
25+
let originalForceIgnore: string;
26+
let conn: Connection;
27+
828
describe('forceignore changes', () => {
9-
before(() => {
10-
// source status to init tracking
29+
before(async () => {
30+
session = await TestSession.create({
31+
project: {
32+
name: 'IgnoreProject',
33+
},
34+
setupCommands: [
35+
`sfdx force:org:create -d 1 -s -f ${path.join('config', 'project-scratch-def.json')}`,
36+
`sfdx force:apex:class:create -n IgnoreTest --outputdir ${classdir}`,
37+
],
38+
});
39+
originalForceIgnore = await fs.readFile(path.join(session.project.dir, '.forceignore'), 'utf8');
40+
conn = await Connection.create({
41+
authInfo: await AuthInfo.create({
42+
username: (session.setup[0] as { result: { username: string } }).result?.username,
43+
}),
44+
});
1145
});
1246

13-
it.skip('will not push a file that was created, then ignored', () => {
14-
// add a file in the local source
15-
// setup a forceIgnore with some file
16-
// push
17-
// verify not in results
47+
after(async () => {
48+
await session?.zip(undefined, 'artifacts');
49+
await session?.clean();
1850
});
1951

20-
it.skip('will not push a file that was created, then un-ignored', () => {
21-
// setup a forceIgnore with some file
22-
// add a file in the local source
23-
// unignore the file
24-
// push
25-
// verify file pushed in results
52+
describe('local', () => {
53+
it('will not push a file that was created, then ignored', async () => {
54+
// setup a forceIgnore with some file
55+
const newForceIgnore = originalForceIgnore + '\n' + `${classdir}/IgnoreTest.cls`;
56+
await fs.writeFile(path.join(session.project.dir, '.forceignore'), newForceIgnore);
57+
// nothing should push
58+
const output = execCmd<PushPullResponse[]>('force:source:push --json', { ensureExitCode: 0 }).jsonOutput.result;
59+
expect(output).to.deep.equal([]);
60+
});
61+
62+
it('will ignore a class in the ignore file before it was created', async () => {
63+
// setup a forceIgnore with some file
64+
const newForceIgnore =
65+
originalForceIgnore + '\n' + `${classdir}/UnIgnoreTest.cls` + '\n' + `${classdir}/IgnoreTest.cls`;
66+
await fs.writeFile(path.join(session.project.dir, '.forceignore'), newForceIgnore);
67+
68+
// add a file in the local source
69+
shell.exec(`sfdx force:apex:class:create -n UnIgnoreTest --outputdir ${classdir}`, {
70+
cwd: session.project.dir,
71+
silent: true,
72+
});
73+
// pushes with no results
74+
const ignoredOutput = execCmd<PushPullResponse[]>('force:source:push --json', { ensureExitCode: 0 }).jsonOutput
75+
.result;
76+
// nothing should have been pushed
77+
expect(ignoredOutput).to.deep.equal([]);
78+
});
79+
80+
it('will push files that are now un-ignored', async () => {
81+
// un-ignore the file
82+
await fs.writeFile(path.join(session.project.dir, '.forceignore'), originalForceIgnore);
83+
84+
// verify file pushed in results
85+
const unIgnoredOutput = execCmd<PushPullResponse[]>('force:source:push --json', { ensureExitCode: 0 }).jsonOutput
86+
.result;
87+
88+
// all 4 files should have been pushed
89+
expect(unIgnoredOutput).to.have.length(4);
90+
unIgnoredOutput.map((result) => expect(result.type === 'ApexClass'));
91+
});
2692
});
2793

28-
it.skip('will not pull a remote file added to the ignore AFTER it is being tracked', () => {
29-
// make a remote change
30-
// add that type to the forceignore
31-
// pull doesn't retrieve that change
94+
describe('remote', () => {
95+
it('adds on the server', async () => {
96+
const createResult = await conn.tooling.create('ApexClass', {
97+
Name: 'CreatedClass',
98+
Body: 'public class CreatedClass {}',
99+
Status: 'Active',
100+
});
101+
if (!Array.isArray(createResult) && createResult.success) {
102+
expect(createResult.id).to.be.a('string');
103+
}
104+
});
105+
106+
it('will not pull a remote file added to the ignore AFTER it is being tracked', async () => {
107+
// add that type to the forceignore
108+
await fs.writeFile(path.join(session.project.dir, '.forceignore'), originalForceIgnore + '\n' + classdir);
109+
110+
// gets file into source tracking
111+
const statusOutput = execCmd<StatusResult[]>('force:source:status --json --remote', { ensureExitCode: 0 })
112+
.jsonOutput.result;
113+
expect(statusOutput.some((result) => result.fullName === 'CreatedClass')).to.equal(true);
114+
115+
// pull doesn't retrieve that change
116+
const pullOutput = execCmd<PushPullResponse[]>('force:source:pull --json', { ensureExitCode: 0 }).jsonOutput
117+
.result;
118+
expect(pullOutput.some((result) => result.fullName === 'CreatedClass')).to.equal(false);
119+
});
32120
});
33121
});

test/nuts/commands/remoteChanges.nut.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ describe('remote changes', () => {
2929
setupCommands: [`sfdx force:org:create -d 1 -s -f ${path.join('config', 'project-scratch-def.json')}`],
3030
});
3131
conn = await Connection.create({
32-
authInfo: await AuthInfo.create({ username: session.setup[0].result?.username as string }),
32+
authInfo: await AuthInfo.create({
33+
username: (session.setup[0] as { result: { username: string } }).result?.username,
34+
}),
3335
});
3436
});
3537

test/nuts/commands/resetClear.nut.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ describe('reset and clear', () => {
3737
},
3838
setupCommands: [`sfdx force:org:create -d 1 -s -f ${path.join('config', 'project-scratch-def.json')}`],
3939
});
40-
orgId = session.setup[0].result?.orgId;
40+
orgId = (session.setup[0] as { result: { orgId: string } }).result?.orgId;
4141
trackingFileFolder = path.join(session?.project.dir, '.sfdx', 'orgs', orgId);
4242
conn = await Connection.create({
43-
authInfo: await AuthInfo.create({ username: session.setup[0].result?.username as string }),
43+
authInfo: await AuthInfo.create({
44+
username: (session.setup[0] as { result: { username: string } }).result?.username,
45+
}),
4446
});
4547
});
4648

yarn.lock

+9-1
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@
913913
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650"
914914
integrity sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==
915915

916-
"@types/glob@^7.1.1":
916+
"@types/glob@*", "@types/glob@^7.1.1":
917917
version "7.1.4"
918918
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672"
919919
integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==
@@ -992,6 +992,14 @@
992992
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
993993
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
994994

995+
"@types/shelljs@^0.8.9":
996+
version "0.8.9"
997+
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.9.tgz#45dd8501aa9882976ca3610517dac3831c2fbbf4"
998+
integrity sha512-flVe1dvlrCyQJN/SGrnBxqHG+RzXrVKsmjD8WS/qYHpq5UPjfq7UWFBENP0ZuOl0g6OpAlL6iBoLSvKYUUmyQw==
999+
dependencies:
1000+
"@types/glob" "*"
1001+
"@types/node" "*"
1002+
9951003
"@types/sinon@*", "@types/sinon@^10.0.2":
9961004
version "10.0.2"
9971005
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.2.tgz#f360d2f189c0fd433d14aeb97b9d705d7e4cc0e4"

0 commit comments

Comments
 (0)