apify-test-tools 0.10.2-beta.4 → 0.10.2-beta.5

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.
Files changed (90) hide show
  1. package/.github/cli/apify-test-tools.mjs +125 -127
  2. package/.github/scripts/run-with-apify-tokens.mjs +1 -1
  3. package/CHANGELOG.md +5 -0
  4. package/CONTRIBUTING.md +4 -1
  5. package/README.md +69 -71
  6. package/bin/affected-actors.ts +132 -91
  7. package/bin/build.ts +0 -2
  8. package/bin/diff-changes.ts +7 -6
  9. package/bin/diff-json-schema.ts +5 -6
  10. package/bin/git.ts +21 -55
  11. package/bin/main.ts +7 -97
  12. package/bin/slack.ts +14 -27
  13. package/bin/types.ts +6 -0
  14. package/dist/bin/affected-actors.d.ts +23 -22
  15. package/dist/bin/affected-actors.d.ts.map +1 -1
  16. package/dist/bin/affected-actors.js +108 -60
  17. package/dist/bin/affected-actors.js.map +1 -1
  18. package/dist/bin/build.d.ts +0 -1
  19. package/dist/bin/build.d.ts.map +1 -1
  20. package/dist/bin/build.js +0 -2
  21. package/dist/bin/build.js.map +1 -1
  22. package/dist/bin/diff-changes.d.ts +4 -3
  23. package/dist/bin/diff-changes.d.ts.map +1 -1
  24. package/dist/bin/diff-changes.js +4 -4
  25. package/dist/bin/diff-changes.js.map +1 -1
  26. package/dist/bin/diff-json-schema.d.ts +2 -2
  27. package/dist/bin/diff-json-schema.d.ts.map +1 -1
  28. package/dist/bin/diff-json-schema.js +1 -4
  29. package/dist/bin/diff-json-schema.js.map +1 -1
  30. package/dist/bin/git.d.ts +6 -9
  31. package/dist/bin/git.d.ts.map +1 -1
  32. package/dist/bin/git.js +20 -48
  33. package/dist/bin/git.js.map +1 -1
  34. package/dist/bin/main.js +7 -77
  35. package/dist/bin/main.js.map +1 -1
  36. package/dist/bin/slack.d.ts +2 -7
  37. package/dist/bin/slack.d.ts.map +1 -1
  38. package/dist/bin/slack.js +12 -18
  39. package/dist/bin/slack.js.map +1 -1
  40. package/dist/bin/types.d.ts +5 -0
  41. package/dist/bin/types.d.ts.map +1 -1
  42. package/dist/test/bundle/cli-bundle.test.js +14 -16
  43. package/dist/test/bundle/cli-bundle.test.js.map +1 -1
  44. package/dist/test/e2e/cli-bundle.e2e.test.js +22 -63
  45. package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
  46. package/dist/test/e2e/git-build-release.e2e.test.d.ts +2 -0
  47. package/dist/test/e2e/git-build-release.e2e.test.d.ts.map +1 -0
  48. package/dist/test/e2e/git-build-release.e2e.test.js +87 -0
  49. package/dist/test/e2e/git-build-release.e2e.test.js.map +1 -0
  50. package/dist/test/e2e/helpers.d.ts +37 -0
  51. package/dist/test/e2e/helpers.d.ts.map +1 -0
  52. package/dist/test/e2e/helpers.js +57 -0
  53. package/dist/test/e2e/helpers.js.map +1 -0
  54. package/dist/test/unit/bin/affected-actors-cli.test.d.ts +2 -0
  55. package/dist/test/unit/bin/affected-actors-cli.test.d.ts.map +1 -0
  56. package/dist/test/unit/bin/affected-actors-cli.test.js +161 -0
  57. package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -0
  58. package/dist/test/unit/bin/affected-actors.test.js +65 -78
  59. package/dist/test/unit/bin/affected-actors.test.js.map +1 -1
  60. package/dist/test/unit/bin/build-cli.test.js +5 -13
  61. package/dist/test/unit/bin/build-cli.test.js.map +1 -1
  62. package/dist/test/unit/bin/compiled-cli.d.ts +6 -0
  63. package/dist/test/unit/bin/compiled-cli.d.ts.map +1 -0
  64. package/dist/test/unit/bin/compiled-cli.js +16 -0
  65. package/dist/test/unit/bin/compiled-cli.js.map +1 -0
  66. package/dist/test/unit/bin/diff-changes.test.js +55 -55
  67. package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
  68. package/dist/test/unit/bin/diff.test.js +25 -28
  69. package/dist/test/unit/bin/diff.test.js.map +1 -1
  70. package/dist/test/unit/bin/git.test.js +29 -97
  71. package/dist/test/unit/bin/git.test.js.map +1 -1
  72. package/dist/test/unit/bin/slack.test.js +6 -27
  73. package/dist/test/unit/bin/slack.test.js.map +1 -1
  74. package/dist/test/unit/should-built-and-test.test.js +18 -21
  75. package/dist/test/unit/should-built-and-test.test.js.map +1 -1
  76. package/dist/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +1 -1
  78. package/test/bundle/cli-bundle.test.ts +14 -24
  79. package/test/e2e/cli-bundle.e2e.test.ts +39 -84
  80. package/test/e2e/git-build-release.e2e.test.ts +113 -0
  81. package/test/e2e/helpers.ts +82 -0
  82. package/test/unit/bin/affected-actors-cli.test.ts +193 -0
  83. package/test/unit/bin/affected-actors.test.ts +83 -85
  84. package/test/unit/bin/build-cli.test.ts +6 -12
  85. package/test/unit/bin/compiled-cli.ts +17 -0
  86. package/test/unit/bin/diff-changes.test.ts +55 -55
  87. package/test/unit/bin/diff.test.ts +25 -28
  88. package/test/unit/bin/git.test.ts +37 -120
  89. package/test/unit/bin/slack.test.ts +6 -30
  90. package/test/unit/should-built-and-test.test.ts +19 -22
@@ -1,7 +1,7 @@
1
1
  import type { MockInstance } from 'vitest';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { getAffectedActors, toAffectedActorsDocument } from '../../../bin/affected-actors.js';
4
+ import { getAffectedActors } from '../../../bin/affected-actors.js';
5
5
  import * as DiffChanges from '../../../bin/diff-changes.js';
6
6
  import * as Git from '../../../bin/git.js';
7
7
  import type { ActorConfig, Commit } from '../../../bin/types.js';
@@ -40,84 +40,113 @@ describe('getAffectedActors', () => {
40
40
  });
41
41
 
42
42
  describe('with --base', () => {
43
- let getCommitsSpy: MockInstance;
43
+ const mergeBaseSha = 'b'.repeat(40);
44
+ let commitsBetweenSpy: MockInstance;
44
45
 
45
46
  beforeEach(() => {
46
- vi.spyOn(Git, 'hasMergeFromTarget').mockResolvedValue(false);
47
- getCommitsSpy = vi.spyOn(Git, 'getCommits').mockResolvedValue(commits);
47
+ vi.spyOn(Git, 'getMergeBase').mockResolvedValue(mergeBaseSha);
48
+ commitsBetweenSpy = vi.spyOn(Git, 'getCommitsBetween').mockResolvedValue(commits);
48
49
  });
49
50
 
50
- it('inspects the branch commits after --since', async () => {
51
- const result = await getAffectedActors({
52
- base: 'origin/master',
53
- since: sinceSha,
54
- ...selection,
55
- });
51
+ it("inspects the branch's own changes, from where it forked from or last merged --base", async () => {
52
+ const findCommitSpy = vi.spyOn(Git, 'findCommitSha');
53
+
54
+ const result = await getAffectedActors({ base: 'origin/master', ...selection });
56
55
 
57
56
  expect(result).toStrictEqual({
58
- headSha,
57
+ version: 1,
58
+ head: headSha,
59
59
  commits,
60
60
  changedFiles,
61
61
  changelog: '- Added foo',
62
- functionallyChangedActors: [actorA],
63
- cosmeticallyChangedActors: [actorB],
64
- });
65
- expect(getCommitsSpy).toHaveBeenCalledWith({
66
- sourceBranch: 'HEAD',
67
- targetBranch: 'origin/master',
68
- baseCommit: sinceSha,
62
+ // Only the names, the config file stays the source of truth for the rest
63
+ functionallyChangedActors: [{ actorFullName: 'owner/a' }],
64
+ cosmeticallyChangedActors: [{ actorFullName: 'owner/b' }],
69
65
  });
70
- // From the parent of the oldest commit to the newest one, for the files and the changelog alike
71
- expect(Git.getChangedFilesBetween).toHaveBeenCalledWith(`${commits[0].sha}~`, commits[1].sha);
72
- expect(Git.getChangelogAdditions).toHaveBeenCalledWith(`${commits[0].sha}~`, commits[1].sha, changedFiles);
66
+ expect(Git.getMergeBase).toHaveBeenCalledWith('origin/master', 'HEAD');
67
+ expect(Git.getChangedFilesBetween).toHaveBeenCalledWith(mergeBaseSha, headSha);
68
+ expect(Git.getChangelogAdditions).toHaveBeenCalledWith(mergeBaseSha, headSha, changedFiles);
69
+ expect(commitsBetweenSpy).toHaveBeenCalledWith(mergeBaseSha, headSha);
73
70
  expect(changedActorsSpy).toHaveBeenCalledWith({
74
71
  filepathsChanged: changedFiles,
75
72
  actorConfigs: [actorA, actorB],
76
- commits,
73
+ range: { fromRef: mergeBaseSha, toRef: headSha },
77
74
  });
75
+ expect(findCommitSpy).not.toHaveBeenCalled();
78
76
  });
79
77
 
80
- describe('when the branch merged --base in', () => {
78
+ describe('and --since', () => {
79
+ const sinceFiles = ['README.md'];
80
+
81
81
  beforeEach(() => {
82
- vi.spyOn(Git, 'hasMergeFromTarget').mockResolvedValue(true);
83
- vi.spyOn(Git, 'getBranchOnlyChangedFiles').mockResolvedValue(['own-file']);
82
+ vi.spyOn(Git, 'findCommitSha').mockResolvedValue(sinceSha);
83
+ vi.spyOn(Git, 'getChangedFilesBetween').mockImplementation(async (fromRef) =>
84
+ fromRef === sinceSha ? sinceFiles : changedFiles,
85
+ );
84
86
  });
85
87
 
86
- const getWithOwnChanges = async (own: DiffChanges.ChangedActors) => {
87
- changedActorsSpy.mockImplementation(async ({ filepathsChanged }) =>
88
- filepathsChanged[0] === 'own-file' ? own : { functional: [actorA], cosmetic: [actorB] },
88
+ const getNarrowed = async (
89
+ branch: DiffChanges.ChangedActors,
90
+ sinceValidated: DiffChanges.ChangedActors,
91
+ ) => {
92
+ changedActorsSpy.mockImplementation(async ({ range }) =>
93
+ range.fromRef === sinceSha ? sinceValidated : branch,
89
94
  );
90
- return getAffectedActors({ base: 'master', since: sinceSha, ...selection });
95
+ const result = await getAffectedActors({ base: 'origin/master', since: sinceSha, ...selection });
96
+ return {
97
+ functional: result.functionallyChangedActors.map(({ actorFullName }) => actorFullName),
98
+ cosmetic: result.cosmeticallyChangedActors.map(({ actorFullName }) => actorFullName),
99
+ };
91
100
  };
92
101
 
93
- it("classifies the branch's own files against the whole branch, ignoring --since", async () => {
94
- await getWithOwnChanges({ functional: [actorA], cosmetic: [] });
102
+ it('classifies the changes since --since separately', async () => {
103
+ await getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [actorA], cosmetic: [] });
95
104
  expect(changedActorsSpy).toHaveBeenCalledWith({
96
- filepathsChanged: ['own-file'],
105
+ filepathsChanged: sinceFiles,
97
106
  actorConfigs: [actorA, actorB],
98
- commits,
107
+ range: { fromRef: sinceSha, toRef: headSha },
99
108
  });
100
- expect(getCommitsSpy).toHaveBeenCalledWith({ sourceBranch: 'HEAD', targetBranch: 'master' });
101
109
  });
102
110
 
103
- it('reports everything when the branch has functional changes of its own', async () => {
104
- const result = await getWithOwnChanges({ functional: [actorA], cosmetic: [] });
105
- expect(result.functionallyChangedActors).toStrictEqual([actorA]);
106
- expect(result.cosmeticallyChangedActors).toStrictEqual([actorB]);
111
+ it('still describes the whole branch', async () => {
112
+ const result = await getAffectedActors({ base: 'origin/master', since: sinceSha, ...selection });
113
+ expect(result.commits).toStrictEqual(commits);
114
+ expect(result.changedFiles).toStrictEqual(changedFiles);
115
+ });
116
+
117
+ it('retests an Actor whose validated change meets a new functional change', async () => {
118
+ await expect(
119
+ getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [actorA], cosmetic: [] }),
120
+ ).resolves.toStrictEqual({ functional: ['owner/a'], cosmetic: [] });
107
121
  });
108
122
 
109
- it('reports only cosmetic Actors when its own changes are only cosmetic', async () => {
110
- const result = await getWithOwnChanges({ functional: [], cosmetic: [actorB] });
111
- expect(result.functionallyChangedActors).toStrictEqual([]);
112
- expect(result.cosmeticallyChangedActors).toStrictEqual([actorB]);
123
+ it('leaves out Actors that only the branch changed before --since or only --base changed', async () => {
124
+ await expect(
125
+ getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [actorB], cosmetic: [] }),
126
+ ).resolves.toStrictEqual({ functional: [], cosmetic: [] });
113
127
  });
114
128
 
115
- it('reports nothing when it has no changes of its own', async () => {
116
- const result = await getWithOwnChanges({ functional: [], cosmetic: [] });
117
- expect(result.functionallyChangedActors).toStrictEqual([]);
118
- expect(result.cosmeticallyChangedActors).toStrictEqual([]);
119
- // The range is still reported
120
- expect(result.commits).toStrictEqual(commits);
129
+ it('takes the less impactful change when the two disagree', async () => {
130
+ // A validated functional change, then only cosmetic changes (e.g. a README merged in from base)
131
+ await expect(
132
+ getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [], cosmetic: [actorA] }),
133
+ ).resolves.toStrictEqual({ functional: [], cosmetic: ['owner/a'] });
134
+ // A functional change merged in from base, while the branch only changes the README
135
+ await expect(
136
+ getNarrowed({ functional: [], cosmetic: [actorA] }, { functional: [actorA], cosmetic: [] }),
137
+ ).resolves.toStrictEqual({ functional: [], cosmetic: ['owner/a'] });
138
+ });
139
+
140
+ it.each([
141
+ ['is HEAD (a rerun)', headSha],
142
+ ['is not in the local history', undefined],
143
+ ])('checks the whole branch when --since %s', async (_, found) => {
144
+ vi.spyOn(Git, 'findCommitSha').mockResolvedValue(found);
145
+ const result = await getAffectedActors({ base: 'origin/master', since: sinceSha, ...selection });
146
+ expect(result.functionallyChangedActors).toStrictEqual([{ actorFullName: 'owner/a' }]);
147
+ // With no usable validated commit, the "since" diff falls back to the same range as the
148
+ // base diff, so getChangedActors runs twice for identical ranges instead of once.
149
+ expect(changedActorsSpy).toHaveBeenCalledTimes(2);
121
150
  });
122
151
  });
123
152
  });
@@ -131,54 +160,23 @@ describe('getAffectedActors', () => {
131
160
  commitsBetweenSpy = vi.spyOn(Git, 'getCommitsBetween').mockResolvedValue(commits);
132
161
  });
133
162
 
134
- it('inspects everything after --since up to --head', async () => {
163
+ it('inspects everything after --since up to HEAD', async () => {
135
164
  const result = await getAffectedActors({ since: 'HEAD~2', ...selection });
136
165
 
137
166
  expect(result).toStrictEqual({
138
- headSha,
167
+ version: 1,
168
+ head: headSha,
139
169
  commits,
140
170
  changedFiles,
141
171
  changelog: '- Added foo',
142
- functionallyChangedActors: [actorA],
143
- cosmeticallyChangedActors: [actorB],
172
+ // Only the names, the config file stays the source of truth for the rest
173
+ functionallyChangedActors: [{ actorFullName: 'owner/a' }],
174
+ cosmeticallyChangedActors: [{ actorFullName: 'owner/b' }],
144
175
  });
145
176
  expect(resolveBaseSpy).toHaveBeenCalledWith('HEAD~2');
146
177
  expect(commitsBetweenSpy).toHaveBeenCalledWith(sinceSha, headSha);
147
178
  expect(Git.getChangedFilesBetween).toHaveBeenCalledWith(sinceSha, headSha);
148
179
  expect(Git.getChangelogAdditions).toHaveBeenCalledWith(sinceSha, headSha, changedFiles);
149
180
  });
150
-
151
- it('has no commits when head is the --since commit', async () => {
152
- resolveBaseSpy.mockResolvedValue(headSha);
153
-
154
- const result = await getAffectedActors({ since: headSha, ...selection });
155
-
156
- expect(result.commits).toStrictEqual([]);
157
- // Empty `git log x..x` output doesn't parse as a commit, so it's never run
158
- expect(commitsBetweenSpy).not.toHaveBeenCalled();
159
- });
160
- });
161
- });
162
-
163
- describe('toAffectedActorsDocument', () => {
164
- it('keeps only the Actor names, the config file stays the source of truth for the rest', () => {
165
- expect(
166
- toAffectedActorsDocument({
167
- headSha,
168
- commits,
169
- changedFiles: ['src/main.ts'],
170
- changelog: null,
171
- functionallyChangedActors: [actorA],
172
- cosmeticallyChangedActors: [actorB],
173
- }),
174
- ).toStrictEqual({
175
- version: 1,
176
- head: headSha,
177
- commits,
178
- changedFiles: ['src/main.ts'],
179
- changelog: null,
180
- functionallyChangedActors: [{ actorFullName: 'owner/a' }],
181
- cosmeticallyChangedActors: [{ actorFullName: 'owner/b' }],
182
- });
183
181
  });
184
182
  });
@@ -1,15 +1,12 @@
1
- import { execFileSync, spawnSync } from 'node:child_process';
2
- import { chmod, mkdtemp, rm } from 'node:fs/promises';
3
- import path from 'node:path';
1
+ import { spawnSync } from 'node:child_process';
4
2
  import process from 'node:process';
5
- import { fileURLToPath } from 'node:url';
6
3
 
7
4
  import { afterAll, beforeAll, describe, expect, it } from 'vitest';
8
5
 
9
- const projectRoot = fileURLToPath(new URL('../../../', import.meta.url));
10
- const compilerPath = fileURLToPath(new URL('../../../node_modules/typescript/bin/tsc', import.meta.url));
6
+ import { compileCli } from './compiled-cli.js';
7
+
11
8
  let cliPath: string;
12
- let buildDir: string | undefined;
9
+ let cleanup: (() => Promise<void>) | undefined;
13
10
 
14
11
  const run = (command: string[], input: string, env: NodeJS.ProcessEnv = process.env) =>
15
12
  spawnSync(cliPath, command, { encoding: 'utf8', input, stdio: ['pipe', 'pipe', 'pipe'], env });
@@ -17,14 +14,11 @@ const runBuild = (input: string, args: string[] = ['--version-number', '0.99'])
17
14
 
18
15
  describe('build and notify-release CLI', () => {
19
16
  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);
17
+ ({ cliPath, cleanup } = await compileCli());
24
18
  });
25
19
 
26
20
  afterAll(async () => {
27
- if (buildDir) await rm(buildDir, { recursive: true, force: true });
21
+ await cleanup?.();
28
22
  });
29
23
 
30
24
  it('builds nothing for an empty list and prints an empty build list', () => {
@@ -0,0 +1,17 @@
1
+ import { execFileSync } 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
+ const projectRoot = fileURLToPath(new URL('../../../', import.meta.url));
8
+ const compilerPath = fileURLToPath(new URL('../../../node_modules/typescript/bin/tsc', import.meta.url));
9
+
10
+ /** Compiles the CLI from the sources into a temporary directory, so tests run it the way users do. */
11
+ export const compileCli = async () => {
12
+ const buildDir = await mkdtemp(path.join(projectRoot, '.cli-test-'));
13
+ execFileSync(process.execPath, [compilerPath, '--outDir', buildDir], { cwd: projectRoot });
14
+ const cliPath = path.join(buildDir, 'bin/main.js');
15
+ await chmod(cliPath, 0o755);
16
+ return { cliPath, cleanup: async () => rm(buildDir, { recursive: true, force: true }) };
17
+ };