apify-test-tools 0.10.2-beta.3 → 0.10.2-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/cli/apify-test-tools.mjs +126 -125
- package/.github/scripts/run-with-apify-tokens.mjs +1 -1
- package/.github/workflows/public_pr-build-test.yaml +25 -25
- package/.github/workflows/public_push-build-latest.yaml +20 -4
- package/CHANGELOG.md +1 -0
- package/README.md +67 -17
- package/bin/affected-actors.ts +16 -25
- package/bin/build.ts +55 -38
- package/bin/git.ts +3 -14
- package/bin/main.ts +96 -40
- package/bin/slack.ts +24 -0
- package/bin/stdin-input.ts +65 -0
- package/dist/bin/affected-actors.d.ts +4 -9
- package/dist/bin/affected-actors.d.ts.map +1 -1
- package/dist/bin/affected-actors.js +17 -19
- package/dist/bin/affected-actors.js.map +1 -1
- package/dist/bin/build.d.ts +12 -5
- package/dist/bin/build.d.ts.map +1 -1
- package/dist/bin/build.js +45 -23
- package/dist/bin/build.js.map +1 -1
- package/dist/bin/git.d.ts +1 -6
- package/dist/bin/git.d.ts.map +1 -1
- package/dist/bin/git.js +3 -13
- package/dist/bin/git.js.map +1 -1
- package/dist/bin/main.js +78 -40
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/slack.d.ts +8 -0
- package/dist/bin/slack.d.ts.map +1 -1
- package/dist/bin/slack.js +18 -0
- package/dist/bin/slack.js.map +1 -1
- package/dist/bin/stdin-input.d.ts +17 -0
- package/dist/bin/stdin-input.d.ts.map +1 -0
- package/dist/bin/stdin-input.js +54 -0
- package/dist/bin/stdin-input.js.map +1 -0
- package/dist/test/bundle/cli-bundle.test.js +27 -4
- package/dist/test/bundle/cli-bundle.test.js.map +1 -1
- package/dist/test/e2e/cli-bundle.e2e.test.js +16 -9
- package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
- package/dist/test/unit/bin/affected-actors.test.js +7 -13
- package/dist/test/unit/bin/affected-actors.test.js.map +1 -1
- package/dist/test/unit/bin/build-cli.test.d.ts +2 -0
- package/dist/test/unit/bin/build-cli.test.d.ts.map +1 -0
- package/dist/test/unit/bin/build-cli.test.js +52 -0
- package/dist/test/unit/bin/build-cli.test.js.map +1 -0
- package/dist/test/unit/bin/build.test.js +61 -4
- package/dist/test/unit/bin/build.test.js.map +1 -1
- package/dist/test/unit/bin/git.test.js +2 -14
- package/dist/test/unit/bin/git.test.js.map +1 -1
- package/dist/test/unit/bin/slack.test.js +39 -1
- package/dist/test/unit/bin/slack.test.js.map +1 -1
- package/dist/test/unit/bin/stdin-input.test.d.ts +2 -0
- package/dist/test/unit/bin/stdin-input.test.d.ts.map +1 -0
- package/dist/test/unit/bin/stdin-input.test.js +50 -0
- package/dist/test/unit/bin/stdin-input.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/test/bundle/cli-bundle.test.ts +50 -4
- package/test/e2e/cli-bundle.e2e.test.ts +26 -11
- package/test/unit/bin/affected-actors.test.ts +7 -13
- package/test/unit/bin/build-cli.test.ts +61 -0
- package/test/unit/bin/build.test.ts +71 -3
- package/test/unit/bin/git.test.ts +1 -15
- package/test/unit/bin/slack.test.ts +44 -1
- package/test/unit/bin/stdin-input.test.ts +61 -0
package/package.json
CHANGED
|
@@ -34,11 +34,18 @@ const writeRepoFile = async (file: string, content: string) => {
|
|
|
34
34
|
await writeFile(path.join(repoDir, file), content);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
const result = spawnSync(process.execPath, [bundlePath, ...args], {
|
|
37
|
+
const runCliWithInput = (input: string, ...args: string[]) => {
|
|
38
|
+
const result = spawnSync(process.execPath, [bundlePath, ...args], {
|
|
39
|
+
cwd: repoDir,
|
|
40
|
+
env: cliEnv,
|
|
41
|
+
encoding: 'utf8',
|
|
42
|
+
input,
|
|
43
|
+
});
|
|
39
44
|
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
40
45
|
};
|
|
41
46
|
|
|
47
|
+
const runCli = (...args: string[]) => runCliWithInput('', ...args);
|
|
48
|
+
|
|
42
49
|
const gitRange = ['--source-branch', 'feature', '--target-branch', 'main'];
|
|
43
50
|
|
|
44
51
|
describe('CLI bundle', () => {
|
|
@@ -91,7 +98,9 @@ describe('CLI bundle', () => {
|
|
|
91
98
|
'get-affected-actors',
|
|
92
99
|
'report-tests',
|
|
93
100
|
'build',
|
|
101
|
+
'build-for-test',
|
|
94
102
|
'release',
|
|
103
|
+
'notify-release',
|
|
95
104
|
'build-from-local',
|
|
96
105
|
'delete-old-builds',
|
|
97
106
|
]) {
|
|
@@ -143,13 +152,50 @@ describe('CLI bundle', () => {
|
|
|
143
152
|
});
|
|
144
153
|
|
|
145
154
|
it('build --dry-run with nothing to build needs no token', () => {
|
|
146
|
-
const { status, stdout, stderr } =
|
|
155
|
+
const { status, stdout, stderr } = runCliWithInput(
|
|
156
|
+
JSON.stringify({ actors: [] }),
|
|
157
|
+
'build',
|
|
158
|
+
'--version-number',
|
|
159
|
+
'0.99',
|
|
160
|
+
'--dry-run',
|
|
161
|
+
);
|
|
147
162
|
expect(status, stderr).toBe(0);
|
|
148
163
|
expect(JSON.parse(stdout)).toEqual({ dryRun: true, builds: [] });
|
|
149
164
|
});
|
|
150
165
|
|
|
151
166
|
it('build --dry-run fails cleanly when the Actor token is missing', () => {
|
|
152
|
-
const { status, stderr } =
|
|
167
|
+
const { status, stderr } = runCliWithInput(
|
|
168
|
+
JSON.stringify({ actors: [{ actorFullName: 'tester/foo' }] }),
|
|
169
|
+
'build',
|
|
170
|
+
'--version-number',
|
|
171
|
+
'0.99',
|
|
172
|
+
'--dry-run',
|
|
173
|
+
);
|
|
174
|
+
expect(status).toBe(1);
|
|
175
|
+
expect(stderr).toContain('Env var FOO_TOKEN is not set (needed for actor "tester/foo")');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('build fails on empty input instead of building nothing', () => {
|
|
179
|
+
const { status, stderr } = runCli('build', '--version-number', '0.99', '--dry-run');
|
|
180
|
+
expect(status).toBe(1);
|
|
181
|
+
expect(stderr).toContain('got nothing');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('build-for-test --dry-run with nothing to build needs no token', () => {
|
|
185
|
+
const { status, stdout, stderr } = runCli(
|
|
186
|
+
'build-for-test',
|
|
187
|
+
'--target-branch',
|
|
188
|
+
'main',
|
|
189
|
+
'--actors',
|
|
190
|
+
'tester/bar',
|
|
191
|
+
'--dry-run',
|
|
192
|
+
);
|
|
193
|
+
expect(status, stderr).toBe(0);
|
|
194
|
+
expect(JSON.parse(stdout)).toEqual({ dryRun: true, builds: [] });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('build-for-test --dry-run fails cleanly when the Actor token is missing', () => {
|
|
198
|
+
const { status, stderr } = runCli('build-for-test', '--target-branch', 'main', '--dry-run');
|
|
153
199
|
expect(status).toBe(1);
|
|
154
200
|
expect(stderr).toContain('Env var FOO_TOKEN is not set (needed for actor "tester/foo")');
|
|
155
201
|
});
|
|
@@ -57,17 +57,20 @@ const writeRepoFile = async (file: string, content: string) => {
|
|
|
57
57
|
// retries an unreachable API for minutes, so a hang is killed here and fails with a clear error.
|
|
58
58
|
const PROCESS_TIMEOUT_MS = 8 * 60_000;
|
|
59
59
|
|
|
60
|
-
const
|
|
60
|
+
const runCliWithInput = (input: string, ...args: string[]) => {
|
|
61
61
|
const result = spawnSync(process.execPath, [bundlePath, ...args], {
|
|
62
62
|
cwd: repoDir,
|
|
63
63
|
env: cliEnv,
|
|
64
64
|
encoding: 'utf8',
|
|
65
65
|
timeout: PROCESS_TIMEOUT_MS,
|
|
66
|
+
input,
|
|
66
67
|
});
|
|
67
68
|
const error = result.error ? `\n${result.error.message}` : '';
|
|
68
69
|
return { status: result.status, stdout: result.stdout, stderr: result.stderr + error };
|
|
69
70
|
};
|
|
70
71
|
|
|
72
|
+
const runCli = (...args: string[]) => runCliWithInput('', ...args);
|
|
73
|
+
|
|
71
74
|
interface ActorBuild {
|
|
72
75
|
buildId: string;
|
|
73
76
|
actorRawId: string;
|
|
@@ -117,11 +120,28 @@ describe.runIf(token)('CLI bundle against real Actors', { timeout: 2 * 60_000 },
|
|
|
117
120
|
if (repoDir) await rm(repoDir, { recursive: true, force: true });
|
|
118
121
|
});
|
|
119
122
|
|
|
120
|
-
it('build --dry-run plans a test build of the
|
|
121
|
-
const { status, stdout, stderr } =
|
|
123
|
+
it('build --dry-run plans a test build of the Actors it is given', () => {
|
|
124
|
+
const { status, stdout, stderr } = runCliWithInput(
|
|
125
|
+
JSON.stringify({ actors: [{ actorFullName: ACTORS[0] }] }),
|
|
122
126
|
'build',
|
|
123
|
-
'--
|
|
124
|
-
'
|
|
127
|
+
'--version-number',
|
|
128
|
+
'0.99',
|
|
129
|
+
'--repo-url',
|
|
130
|
+
REPO_URL,
|
|
131
|
+
'--dry-run',
|
|
132
|
+
);
|
|
133
|
+
expect(status, stderr).toBe(0);
|
|
134
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
135
|
+
dryRun: true,
|
|
136
|
+
builds: [
|
|
137
|
+
{ actorFullName: ACTORS[0], versionNumber: '0.99', gitRepoUrl: `${REPO_URL}#feature:actors/dummy-1` },
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('build-for-test --dry-run plans a test build of the changed Actor only', () => {
|
|
143
|
+
const { status, stdout, stderr } = runCli(
|
|
144
|
+
'build-for-test',
|
|
125
145
|
'--target-branch',
|
|
126
146
|
'main',
|
|
127
147
|
'--repo-url',
|
|
@@ -132,12 +152,7 @@ describe.runIf(token)('CLI bundle against real Actors', { timeout: 2 * 60_000 },
|
|
|
132
152
|
expect(JSON.parse(stdout)).toEqual({
|
|
133
153
|
dryRun: true,
|
|
134
154
|
builds: [
|
|
135
|
-
{
|
|
136
|
-
actorFullName: ACTORS[0],
|
|
137
|
-
versionNumber: '0.99',
|
|
138
|
-
gitRepoUrl: `${REPO_URL}#feature:actors/dummy-1`,
|
|
139
|
-
useDockerCache: false,
|
|
140
|
-
},
|
|
155
|
+
{ actorFullName: ACTORS[0], versionNumber: '0.99', gitRepoUrl: `${REPO_URL}#feature:actors/dummy-1` },
|
|
141
156
|
],
|
|
142
157
|
});
|
|
143
158
|
});
|
|
@@ -28,13 +28,12 @@ describe('getAffectedActors', () => {
|
|
|
28
28
|
.spyOn(DiffChanges, 'getChangedActors')
|
|
29
29
|
.mockResolvedValue({ functional: [actorA], cosmetic: [actorB] });
|
|
30
30
|
vi.spyOn(Git, 'resolveCommitSha').mockResolvedValue(headSha);
|
|
31
|
-
vi.spyOn(Git, 'resolveBranchName').mockResolvedValue('feature');
|
|
32
31
|
vi.spyOn(Git, 'getChangedFilesBetween').mockResolvedValue(changedFiles);
|
|
33
32
|
vi.spyOn(Git, 'getChangelogAdditions').mockResolvedValue('- Added foo');
|
|
34
33
|
});
|
|
35
34
|
|
|
36
35
|
it('requires --base or --since', async () => {
|
|
37
|
-
const options = {
|
|
36
|
+
const options = { ...selection };
|
|
38
37
|
await expect(getAffectedActors(options)).rejects.toThrow('Pass --base, --since, or both');
|
|
39
38
|
// An empty --since (e.g. from an empty cache file) counts as not passed
|
|
40
39
|
await expect(getAffectedActors({ ...options, since: '' })).rejects.toThrow('Pass --base, --since, or both');
|
|
@@ -50,7 +49,6 @@ describe('getAffectedActors', () => {
|
|
|
50
49
|
|
|
51
50
|
it('inspects the branch commits after --since', async () => {
|
|
52
51
|
const result = await getAffectedActors({
|
|
53
|
-
head: 'origin/feature',
|
|
54
52
|
base: 'origin/master',
|
|
55
53
|
since: sinceSha,
|
|
56
54
|
...selection,
|
|
@@ -58,7 +56,6 @@ describe('getAffectedActors', () => {
|
|
|
58
56
|
|
|
59
57
|
expect(result).toStrictEqual({
|
|
60
58
|
headSha,
|
|
61
|
-
ref: 'feature',
|
|
62
59
|
commits,
|
|
63
60
|
changedFiles,
|
|
64
61
|
changelog: '- Added foo',
|
|
@@ -66,7 +63,7 @@ describe('getAffectedActors', () => {
|
|
|
66
63
|
cosmeticallyChangedActors: [actorB],
|
|
67
64
|
});
|
|
68
65
|
expect(getCommitsSpy).toHaveBeenCalledWith({
|
|
69
|
-
sourceBranch: '
|
|
66
|
+
sourceBranch: 'HEAD',
|
|
70
67
|
targetBranch: 'origin/master',
|
|
71
68
|
baseCommit: sinceSha,
|
|
72
69
|
});
|
|
@@ -90,7 +87,7 @@ describe('getAffectedActors', () => {
|
|
|
90
87
|
changedActorsSpy.mockImplementation(async ({ filepathsChanged }) =>
|
|
91
88
|
filepathsChanged[0] === 'own-file' ? own : { functional: [actorA], cosmetic: [actorB] },
|
|
92
89
|
);
|
|
93
|
-
return getAffectedActors({
|
|
90
|
+
return getAffectedActors({ base: 'master', since: sinceSha, ...selection });
|
|
94
91
|
};
|
|
95
92
|
|
|
96
93
|
it("classifies the branch's own files against the whole branch, ignoring --since", async () => {
|
|
@@ -100,7 +97,7 @@ describe('getAffectedActors', () => {
|
|
|
100
97
|
actorConfigs: [actorA, actorB],
|
|
101
98
|
commits,
|
|
102
99
|
});
|
|
103
|
-
expect(getCommitsSpy).toHaveBeenCalledWith({ sourceBranch: '
|
|
100
|
+
expect(getCommitsSpy).toHaveBeenCalledWith({ sourceBranch: 'HEAD', targetBranch: 'master' });
|
|
104
101
|
});
|
|
105
102
|
|
|
106
103
|
it('reports everything when the branch has functional changes of its own', async () => {
|
|
@@ -135,18 +132,17 @@ describe('getAffectedActors', () => {
|
|
|
135
132
|
});
|
|
136
133
|
|
|
137
134
|
it('inspects everything after --since up to --head', async () => {
|
|
138
|
-
const result = await getAffectedActors({
|
|
135
|
+
const result = await getAffectedActors({ since: 'HEAD~2', ...selection });
|
|
139
136
|
|
|
140
137
|
expect(result).toStrictEqual({
|
|
141
138
|
headSha,
|
|
142
|
-
ref: 'feature',
|
|
143
139
|
commits,
|
|
144
140
|
changedFiles,
|
|
145
141
|
changelog: '- Added foo',
|
|
146
142
|
functionallyChangedActors: [actorA],
|
|
147
143
|
cosmeticallyChangedActors: [actorB],
|
|
148
144
|
});
|
|
149
|
-
expect(resolveBaseSpy).toHaveBeenCalledWith('HEAD~2'
|
|
145
|
+
expect(resolveBaseSpy).toHaveBeenCalledWith('HEAD~2');
|
|
150
146
|
expect(commitsBetweenSpy).toHaveBeenCalledWith(sinceSha, headSha);
|
|
151
147
|
expect(Git.getChangedFilesBetween).toHaveBeenCalledWith(sinceSha, headSha);
|
|
152
148
|
expect(Git.getChangelogAdditions).toHaveBeenCalledWith(sinceSha, headSha, changedFiles);
|
|
@@ -155,7 +151,7 @@ describe('getAffectedActors', () => {
|
|
|
155
151
|
it('has no commits when head is the --since commit', async () => {
|
|
156
152
|
resolveBaseSpy.mockResolvedValue(headSha);
|
|
157
153
|
|
|
158
|
-
const result = await getAffectedActors({
|
|
154
|
+
const result = await getAffectedActors({ since: headSha, ...selection });
|
|
159
155
|
|
|
160
156
|
expect(result.commits).toStrictEqual([]);
|
|
161
157
|
// Empty `git log x..x` output doesn't parse as a commit, so it's never run
|
|
@@ -169,7 +165,6 @@ describe('toAffectedActorsDocument', () => {
|
|
|
169
165
|
expect(
|
|
170
166
|
toAffectedActorsDocument({
|
|
171
167
|
headSha,
|
|
172
|
-
ref: null,
|
|
173
168
|
commits,
|
|
174
169
|
changedFiles: ['src/main.ts'],
|
|
175
170
|
changelog: null,
|
|
@@ -178,7 +173,6 @@ describe('toAffectedActorsDocument', () => {
|
|
|
178
173
|
}),
|
|
179
174
|
).toStrictEqual({
|
|
180
175
|
version: 1,
|
|
181
|
-
ref: null,
|
|
182
176
|
head: headSha,
|
|
183
177
|
commits,
|
|
184
178
|
changedFiles: ['src/main.ts'],
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import { chmod, mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
8
|
+
|
|
9
|
+
const projectRoot = fileURLToPath(new URL('../../../', import.meta.url));
|
|
10
|
+
const compilerPath = fileURLToPath(new URL('../../../node_modules/typescript/bin/tsc', import.meta.url));
|
|
11
|
+
let cliPath: string;
|
|
12
|
+
let buildDir: string | undefined;
|
|
13
|
+
|
|
14
|
+
const run = (command: string[], input: string, env: NodeJS.ProcessEnv = process.env) =>
|
|
15
|
+
spawnSync(cliPath, command, { encoding: 'utf8', input, stdio: ['pipe', 'pipe', 'pipe'], env });
|
|
16
|
+
const runBuild = (input: string, args: string[] = ['--version-number', '0.99']) => run(['build', ...args], input);
|
|
17
|
+
|
|
18
|
+
describe('build and notify-release CLI', () => {
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
buildDir = await mkdtemp(path.join(projectRoot, '.cli-test-'));
|
|
21
|
+
execFileSync(process.execPath, [compilerPath, '--outDir', buildDir], { cwd: projectRoot });
|
|
22
|
+
cliPath = path.join(buildDir, 'bin/main.js');
|
|
23
|
+
await chmod(cliPath, 0o755);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
if (buildDir) await rm(buildDir, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('builds nothing for an empty list and prints an empty build list', () => {
|
|
31
|
+
const result = runBuild('{"actors":[]}');
|
|
32
|
+
expect(result.status, result.stderr).toBe(0);
|
|
33
|
+
expect(JSON.parse(result.stdout)).toStrictEqual([]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('fails on empty stdin instead of building nothing', () => {
|
|
37
|
+
const result = runBuild('');
|
|
38
|
+
expect(result.status).toBe(1);
|
|
39
|
+
expect(result.stderr).toContain('Expected the Actors to build as JSON on stdin, got nothing');
|
|
40
|
+
expect(result.stdout).toBe('');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('rejects a malformed --version-number', () => {
|
|
44
|
+
const result = runBuild('{"actors":[]}', ['--version-number', 'latest']);
|
|
45
|
+
expect(result.status).toBe(1);
|
|
46
|
+
expect(result.stderr).toContain('--version-number must look like 0.1 or be "default"');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('requires --version-number', () => {
|
|
50
|
+
const result = runBuild('{"actors":[]}', []);
|
|
51
|
+
expect(result.status).toBe(1);
|
|
52
|
+
expect(result.stderr).toContain('Missing required argument: version-number');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('notify-release announces nothing when there are no new commits', () => {
|
|
56
|
+
const document = { commits: [], changedFiles: [], changelog: null, functionallyChangedActors: [] };
|
|
57
|
+
const result = run(['notify-release'], JSON.stringify(document));
|
|
58
|
+
expect(result.status, result.stderr).toBe(0);
|
|
59
|
+
expect(result.stderr).toContain('No new commits, so there is no release to announce');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -7,11 +7,15 @@ import { ActorSourceType, ApifyClient } from 'apify-client';
|
|
|
7
7
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
+
ApifyBuilder,
|
|
10
11
|
assertRepoUrlMatchesDefaultVersion,
|
|
12
|
+
buildActors,
|
|
11
13
|
deleteOldBuilds,
|
|
12
14
|
resolveDefaultVersion,
|
|
13
15
|
runBuilds,
|
|
14
16
|
} from '../../../bin/build.js';
|
|
17
|
+
import * as Git from '../../../bin/git.js';
|
|
18
|
+
import { logger } from '../../../bin/logger.js';
|
|
15
19
|
import type { ActorConfig } from '../../../bin/types.js';
|
|
16
20
|
import { ExistingDir, ExistingFile, RelativeDir } from '../../../bin/utils/path/repo-relative.js';
|
|
17
21
|
|
|
@@ -84,6 +88,71 @@ describe('assertRepoUrlMatchesDefaultVersion', () => {
|
|
|
84
88
|
});
|
|
85
89
|
});
|
|
86
90
|
|
|
91
|
+
describe('runBuilds version number', () => {
|
|
92
|
+
// Only the name and folder of the Actor matter here, the builder is stubbed
|
|
93
|
+
const actorConfig = { actorFullName: ACTOR, folder: { isRoot: () => true } } as unknown as ActorConfig;
|
|
94
|
+
// Restored after each test, so the stub never reaches the tests that run the real builder
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
vi.mocked(ApifyBuilder.fromActorConfig).mockRestore();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const dryRunBuild = async (versionNumber: string, actorInfo = makeActorInfo()) => {
|
|
100
|
+
vi.spyOn(ApifyBuilder, 'fromActorConfig').mockReturnValue({
|
|
101
|
+
getActorInfo: async () => actorInfo,
|
|
102
|
+
} as unknown as ApifyBuilder);
|
|
103
|
+
const result = await runBuilds({
|
|
104
|
+
actorConfigs: [actorConfig],
|
|
105
|
+
versionNumber,
|
|
106
|
+
repoUrl: REPO_URL,
|
|
107
|
+
shouldVerifyRepoUrl: false,
|
|
108
|
+
branch: 'master',
|
|
109
|
+
dryRun: true,
|
|
110
|
+
});
|
|
111
|
+
if (Array.isArray(result)) throw new Error('A dry run must return a plan, not builds');
|
|
112
|
+
return result.builds[0].versionNumber;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
it('builds the version it is given', async () => {
|
|
116
|
+
await expect(dryRunBuild('0.99')).resolves.toBe('0.99');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("resolves default to the version the Actor's default build tag points to", async () => {
|
|
120
|
+
await expect(dryRunBuild('default')).resolves.toBe('0.1');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('warns when the default version does not give its builds the default build tag', async () => {
|
|
124
|
+
const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
125
|
+
await dryRunBuild('default');
|
|
126
|
+
// The fixture's version 0.1 has no build tag of its own
|
|
127
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('users keep running the previous build'));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does not warn when the default version gives its builds the default build tag', async () => {
|
|
131
|
+
const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
132
|
+
const versions = [{ versionNumber: '0.1', buildTag: 'latest', sourceType: ActorSourceType.GitRepo }];
|
|
133
|
+
await dryRunBuild('default', makeActorInfo({ versions } as Partial<ApifyClientTypes.Actor>));
|
|
134
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('buildActors', () => {
|
|
139
|
+
it('builds nothing and needs no branch or repository when there are no Actors', async () => {
|
|
140
|
+
const branchSpy = vi.spyOn(Git, 'getCurrentBranch');
|
|
141
|
+
const repoSpy = vi.spyOn(Git, 'resolveRepoUrl');
|
|
142
|
+
|
|
143
|
+
await expect(buildActors({ actorConfigs: [], versionNumber: '0.99', dryRun: false })).resolves.toStrictEqual(
|
|
144
|
+
[],
|
|
145
|
+
);
|
|
146
|
+
// A dry run keeps the dry-run shape even with nothing to build
|
|
147
|
+
await expect(buildActors({ actorConfigs: [], versionNumber: '0.99', dryRun: true })).resolves.toStrictEqual({
|
|
148
|
+
dryRun: true,
|
|
149
|
+
builds: [],
|
|
150
|
+
});
|
|
151
|
+
expect(branchSpy).not.toHaveBeenCalled();
|
|
152
|
+
expect(repoSpy).not.toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
87
156
|
describe('dry run', () => {
|
|
88
157
|
const TOKEN_ENV_VAR = 'APIFY_TOKEN_OWNER';
|
|
89
158
|
const originalCwd = process.cwd();
|
|
@@ -150,7 +219,7 @@ describe('dry run', () => {
|
|
|
150
219
|
shouldVerifyRepoUrl: true,
|
|
151
220
|
branch: 'feature',
|
|
152
221
|
dryRun: true,
|
|
153
|
-
|
|
222
|
+
versionNumber: '0.99',
|
|
154
223
|
});
|
|
155
224
|
|
|
156
225
|
it('should return a build plan without creating versions or builds', async () => {
|
|
@@ -161,7 +230,6 @@ describe('dry run', () => {
|
|
|
161
230
|
actorFullName: ACTOR,
|
|
162
231
|
versionNumber: '0.99',
|
|
163
232
|
gitRepoUrl: `${REPO_URL}#feature:actors/a`,
|
|
164
|
-
useDockerCache: false,
|
|
165
233
|
},
|
|
166
234
|
],
|
|
167
235
|
});
|
|
@@ -182,7 +250,7 @@ describe('dry run', () => {
|
|
|
182
250
|
shouldVerifyRepoUrl: true,
|
|
183
251
|
branch: 'feature',
|
|
184
252
|
dryRun: true,
|
|
185
|
-
|
|
253
|
+
versionNumber: '0.99',
|
|
186
254
|
}),
|
|
187
255
|
).rejects.toThrow('Repository mismatch');
|
|
188
256
|
});
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
hasMergeFromTarget,
|
|
13
13
|
normalizeRepoUrl,
|
|
14
14
|
parseBaseCommit,
|
|
15
|
-
resolveBranchName,
|
|
16
15
|
resolveCommitSha,
|
|
17
16
|
resolveReleaseBaseCommit,
|
|
18
17
|
resolveRepoUrl,
|
|
@@ -323,7 +322,7 @@ describe('getCurrentBranch', () => {
|
|
|
323
322
|
const gitCommandSpy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue('master');
|
|
324
323
|
await expect(getCurrentBranch()).resolves.toBe('master');
|
|
325
324
|
expect(gitCommandSpy).toHaveBeenCalledWith(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
326
|
-
operation: 'determine the
|
|
325
|
+
operation: 'determine the checked-out branch',
|
|
327
326
|
});
|
|
328
327
|
});
|
|
329
328
|
|
|
@@ -532,16 +531,3 @@ describe('resolveCommitSha', () => {
|
|
|
532
531
|
});
|
|
533
532
|
});
|
|
534
533
|
});
|
|
535
|
-
|
|
536
|
-
describe('resolveBranchName', () => {
|
|
537
|
-
it.each([
|
|
538
|
-
['a local branch', 'refs/heads/feature/foo', 'feature/foo'],
|
|
539
|
-
['a remote branch, dropping the remote', 'refs/remotes/origin/feature/foo', 'feature/foo'],
|
|
540
|
-
['a tag', 'refs/tags/v1.0.0', null],
|
|
541
|
-
['a detached HEAD', 'HEAD', null],
|
|
542
|
-
['a SHA', '', null],
|
|
543
|
-
])('handles %s', async (_, fullName, expected) => {
|
|
544
|
-
vi.spyOn(Utils, 'runGitCommand').mockResolvedValue(fullName);
|
|
545
|
-
await expect(resolveBranchName('some-ref')).resolves.toBe(expected);
|
|
546
|
-
});
|
|
547
|
-
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WebClient } from '@slack/web-api';
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { notifyToSlack } from '../../../bin/slack.js';
|
|
4
|
+
import { announceRelease, notifyToSlack } from '../../../bin/slack.js';
|
|
5
5
|
|
|
6
6
|
vi.mock('@slack/web-api', () => ({ WebClient: vi.fn() }));
|
|
7
7
|
|
|
@@ -63,3 +63,46 @@ describe('notifyToSlack', () => {
|
|
|
63
63
|
expect(postMessage).toHaveBeenCalledTimes(2);
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
describe('announceRelease', () => {
|
|
68
|
+
const commit = (author: string, message: string) => ({ sha: '1', author, date: '', message });
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
vi.stubEnv(TOKEN_ENV_VAR, 'token');
|
|
72
|
+
vi.mocked(WebClient).mockImplementation(function () {
|
|
73
|
+
return { chat: { postMessage }, auth: { test: authTest } };
|
|
74
|
+
} as never);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
vi.unstubAllEnvs();
|
|
79
|
+
vi.clearAllMocks();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should announce nothing, and need no token, when there are no new commits', async () => {
|
|
83
|
+
vi.stubEnv(TOKEN_ENV_VAR, '');
|
|
84
|
+
await announceRelease({
|
|
85
|
+
commits: [],
|
|
86
|
+
changedFiles: [],
|
|
87
|
+
changelog: null,
|
|
88
|
+
dryRun: false,
|
|
89
|
+
reportSlackChannel: '#notif',
|
|
90
|
+
});
|
|
91
|
+
expect(WebClient).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should announce the release by the author of the newest commit, named after the repository', async () => {
|
|
95
|
+
await announceRelease({
|
|
96
|
+
commits: [commit('Old<old@example.com>', 'fix: older'), commit('New<new@example.com>', 'feat: newer')],
|
|
97
|
+
changedFiles: ['src/main.ts'],
|
|
98
|
+
changelog: '- Added foo',
|
|
99
|
+
repoUrl: 'git@github.com:owner/my-repo',
|
|
100
|
+
dryRun: false,
|
|
101
|
+
reportSlackChannel: '#notif',
|
|
102
|
+
});
|
|
103
|
+
expect(postMessage).toHaveBeenCalledWith({
|
|
104
|
+
channel: '#notif',
|
|
105
|
+
text: expect.stringContaining('*my-repo* – New release (by New)'),
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { parseBuildInput, parseReleaseChanges } from '../../../bin/stdin-input.js';
|
|
4
|
+
|
|
5
|
+
describe('parseBuildInput', () => {
|
|
6
|
+
it('reads the Actors', () => {
|
|
7
|
+
expect(parseBuildInput('{"actors":[{"actorFullName":"owner/a"}]}')).toStrictEqual({
|
|
8
|
+
actors: [{ actorFullName: 'owner/a' }],
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('ignores the other fields, so whole get-affected-actors fields can be passed along', () => {
|
|
13
|
+
const input = { head: 'f'.repeat(40), ref: 'feature', actors: [{ actorFullName: 'owner/a', extra: 1 }] };
|
|
14
|
+
expect(parseBuildInput(JSON.stringify(input))).toStrictEqual({ actors: [{ actorFullName: 'owner/a' }] });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accepts an empty list, which builds nothing', () => {
|
|
18
|
+
expect(parseBuildInput('{"actors":[]}')).toStrictEqual({ actors: [] });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('rejects empty input, which is what a failed command earlier in a pipe leaves behind', () => {
|
|
22
|
+
expect(() => parseBuildInput(' \n')).toThrow('Expected the Actors to build as JSON on stdin, got nothing');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('rejects input that is not JSON', () => {
|
|
26
|
+
expect(() => parseBuildInput('owner/a')).toThrow('not valid JSON');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('rejects JSON that is not a list of Actors, e.g. a whole get-affected-actors document', () => {
|
|
30
|
+
expect(() => parseBuildInput('{"functionallyChangedActors":[{"actorFullName":"owner/a"}]}')).toThrow(
|
|
31
|
+
'is not the Actors to build',
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('parseReleaseChanges', () => {
|
|
37
|
+
const commit = { sha: '1'.repeat(40), author: 'Dev<dev@example.com>', date: 'Date', message: 'feat: foo' };
|
|
38
|
+
|
|
39
|
+
it('reads what changed from a get-affected-actors document, ignoring the rest', () => {
|
|
40
|
+
const document = {
|
|
41
|
+
version: 1,
|
|
42
|
+
ref: 'master',
|
|
43
|
+
head: '1'.repeat(40),
|
|
44
|
+
commits: [commit],
|
|
45
|
+
changedFiles: ['src/main.ts'],
|
|
46
|
+
changelog: '- Added foo',
|
|
47
|
+
functionallyChangedActors: [{ actorFullName: 'owner/a' }],
|
|
48
|
+
cosmeticallyChangedActors: [],
|
|
49
|
+
};
|
|
50
|
+
expect(parseReleaseChanges(JSON.stringify(document))).toStrictEqual({
|
|
51
|
+
commits: [commit],
|
|
52
|
+
changedFiles: ['src/main.ts'],
|
|
53
|
+
changelog: '- Added foo',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('rejects empty input and input that is not a get-affected-actors document', () => {
|
|
58
|
+
expect(() => parseReleaseChanges('')).toThrow('got nothing');
|
|
59
|
+
expect(() => parseReleaseChanges('{"actors":[]}')).toThrow('not the output of get-affected-actors');
|
|
60
|
+
});
|
|
61
|
+
});
|