apify-test-tools 0.10.2-beta.2 → 0.10.2-beta.20

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 (275) hide show
  1. package/.gitattributes +1 -0
  2. package/.github/actionlint.yaml +11 -0
  3. package/.github/actions/checkout-restore-dependencies/action.yaml +58 -6
  4. package/.github/cli/apify-test-tools.mjs +135 -142
  5. package/.github/scripts/mask-config-secrets.sh +17 -0
  6. package/.github/scripts/run-with-apify-tokens.mjs +63 -103
  7. package/.github/workflows/_check_code.yaml +5 -21
  8. package/.github/workflows/_move_major_tag.yaml +2 -2
  9. package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +89 -8
  10. package/.github/workflows/public_platform-tests.yaml +68 -30
  11. package/.github/workflows/public_pr-build-test.yaml +77 -38
  12. package/.github/workflows/public_push-build-latest.yaml +49 -11
  13. package/.github/workflows/public_review.yaml +5 -4
  14. package/.github/workflows/public_stale-issues.yaml +51 -0
  15. package/.husky/pre-commit +4 -4
  16. package/.prettierignore +2 -1
  17. package/CHANGELOG.md +22 -0
  18. package/CONTRIBUTING.md +117 -17
  19. package/README.md +221 -595
  20. package/bin/actor-ids.ts +41 -0
  21. package/bin/affected-actors.ts +211 -0
  22. package/bin/build-from-local.ts +19 -1
  23. package/bin/build.ts +105 -39
  24. package/bin/diff-changes.ts +69 -36
  25. package/bin/diff-json-schema.ts +5 -6
  26. package/bin/git.ts +73 -175
  27. package/bin/main.ts +142 -189
  28. package/bin/notify.ts +198 -0
  29. package/bin/run-tests.ts +167 -0
  30. package/bin/slack.ts +16 -76
  31. package/bin/stdin-input.ts +156 -0
  32. package/bin/test-files-glob.ts +126 -0
  33. package/bin/test-report.ts +141 -140
  34. package/bin/types.ts +4 -7
  35. package/bin/utils/config/load-config.ts +20 -2
  36. package/bin/utils/config/parser.ts +40 -8
  37. package/bin/utils/config/structures/grouped.ts +2 -2
  38. package/bin/utils/config/structures/legacy.ts +1 -1
  39. package/dist/bin/actor-ids.d.ts +15 -0
  40. package/dist/bin/actor-ids.d.ts.map +1 -0
  41. package/dist/bin/actor-ids.js +30 -0
  42. package/dist/bin/actor-ids.js.map +1 -0
  43. package/dist/bin/affected-actors.d.ts +55 -0
  44. package/dist/bin/affected-actors.d.ts.map +1 -0
  45. package/dist/bin/affected-actors.js +142 -0
  46. package/dist/bin/affected-actors.js.map +1 -0
  47. package/dist/bin/build-from-local.d.ts +2 -1
  48. package/dist/bin/build-from-local.d.ts.map +1 -1
  49. package/dist/bin/build-from-local.js +6 -2
  50. package/dist/bin/build-from-local.js.map +1 -1
  51. package/dist/bin/build.d.ts +18 -5
  52. package/dist/bin/build.d.ts.map +1 -1
  53. package/dist/bin/build.js +74 -24
  54. package/dist/bin/build.js.map +1 -1
  55. package/dist/bin/diff-changes.d.ts +21 -5
  56. package/dist/bin/diff-changes.d.ts.map +1 -1
  57. package/dist/bin/diff-changes.js +41 -28
  58. package/dist/bin/diff-changes.js.map +1 -1
  59. package/dist/bin/diff-json-schema.d.ts +2 -2
  60. package/dist/bin/diff-json-schema.d.ts.map +1 -1
  61. package/dist/bin/diff-json-schema.js +1 -4
  62. package/dist/bin/diff-json-schema.js.map +1 -1
  63. package/dist/bin/git.d.ts +26 -34
  64. package/dist/bin/git.d.ts.map +1 -1
  65. package/dist/bin/git.js +60 -143
  66. package/dist/bin/git.js.map +1 -1
  67. package/dist/bin/main.js +126 -140
  68. package/dist/bin/main.js.map +1 -1
  69. package/dist/bin/notify.d.ts +35 -0
  70. package/dist/bin/notify.d.ts.map +1 -0
  71. package/dist/bin/notify.js +131 -0
  72. package/dist/bin/notify.js.map +1 -0
  73. package/dist/bin/run-tests.d.ts +21 -0
  74. package/dist/bin/run-tests.d.ts.map +1 -0
  75. package/dist/bin/run-tests.js +138 -0
  76. package/dist/bin/run-tests.js.map +1 -0
  77. package/dist/bin/slack.d.ts +2 -14
  78. package/dist/bin/slack.d.ts.map +1 -1
  79. package/dist/bin/slack.js +17 -46
  80. package/dist/bin/slack.js.map +1 -1
  81. package/dist/bin/stdin-input.d.ts +79 -0
  82. package/dist/bin/stdin-input.d.ts.map +1 -0
  83. package/dist/bin/stdin-input.js +124 -0
  84. package/dist/bin/stdin-input.js.map +1 -0
  85. package/dist/bin/test-files-glob.d.ts +18 -0
  86. package/dist/bin/test-files-glob.d.ts.map +1 -0
  87. package/dist/bin/test-files-glob.js +108 -0
  88. package/dist/bin/test-files-glob.js.map +1 -0
  89. package/dist/bin/test-report.d.ts +69 -8
  90. package/dist/bin/test-report.d.ts.map +1 -1
  91. package/dist/bin/test-report.js +76 -67
  92. package/dist/bin/test-report.js.map +1 -1
  93. package/dist/bin/types.d.ts +4 -7
  94. package/dist/bin/types.d.ts.map +1 -1
  95. package/dist/bin/utils/config/load-config.d.ts +8 -0
  96. package/dist/bin/utils/config/load-config.d.ts.map +1 -1
  97. package/dist/bin/utils/config/load-config.js +14 -1
  98. package/dist/bin/utils/config/load-config.js.map +1 -1
  99. package/dist/bin/utils/config/parser.d.ts +15 -2
  100. package/dist/bin/utils/config/parser.d.ts.map +1 -1
  101. package/dist/bin/utils/config/parser.js +29 -8
  102. package/dist/bin/utils/config/parser.js.map +1 -1
  103. package/dist/bin/utils/config/structures/grouped.js +2 -2
  104. package/dist/bin/utils/config/structures/grouped.js.map +1 -1
  105. package/dist/bin/utils/config/structures/legacy.js +1 -1
  106. package/dist/bin/utils/config/structures/legacy.js.map +1 -1
  107. package/dist/lib/extend-expect.d.ts.map +1 -1
  108. package/dist/lib/extend-expect.js +37 -218
  109. package/dist/lib/extend-expect.js.map +1 -1
  110. package/dist/lib/lib.d.ts +1 -5
  111. package/dist/lib/lib.d.ts.map +1 -1
  112. package/dist/lib/lib.js +19 -31
  113. package/dist/lib/lib.js.map +1 -1
  114. package/dist/lib/run-checks.d.ts +14 -0
  115. package/dist/lib/run-checks.d.ts.map +1 -0
  116. package/dist/lib/run-checks.js +82 -0
  117. package/dist/lib/run-checks.js.map +1 -0
  118. package/dist/lib/run-test-result.d.ts +3 -6
  119. package/dist/lib/run-test-result.d.ts.map +1 -1
  120. package/dist/lib/run-test-result.js +41 -54
  121. package/dist/lib/run-test-result.js.map +1 -1
  122. package/dist/test/bundle/cli-bundle.test.js +76 -56
  123. package/dist/test/bundle/cli-bundle.test.js.map +1 -1
  124. package/dist/test/bundle/token-wrapper.test.d.ts +2 -0
  125. package/dist/test/bundle/token-wrapper.test.d.ts.map +1 -0
  126. package/dist/test/bundle/token-wrapper.test.js +80 -0
  127. package/dist/test/bundle/token-wrapper.test.js.map +1 -0
  128. package/dist/test/e2e/cli-bundle.e2e.test.js +33 -68
  129. package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
  130. package/dist/test/e2e/git-build-release.e2e.test.d.ts +2 -0
  131. package/dist/test/e2e/git-build-release.e2e.test.d.ts.map +1 -0
  132. package/dist/test/e2e/git-build-release.e2e.test.js +87 -0
  133. package/dist/test/e2e/git-build-release.e2e.test.js.map +1 -0
  134. package/dist/test/e2e/helpers.d.ts +37 -0
  135. package/dist/test/e2e/helpers.d.ts.map +1 -0
  136. package/dist/test/e2e/helpers.js +57 -0
  137. package/dist/test/e2e/helpers.js.map +1 -0
  138. package/dist/test/unit/bin/actor-filtering.test.js +7 -0
  139. package/dist/test/unit/bin/actor-filtering.test.js.map +1 -1
  140. package/dist/test/unit/bin/actor-ids.test.d.ts +2 -0
  141. package/dist/test/unit/bin/actor-ids.test.d.ts.map +1 -0
  142. package/dist/test/unit/bin/actor-ids.test.js +40 -0
  143. package/dist/test/unit/bin/actor-ids.test.js.map +1 -0
  144. package/dist/test/unit/bin/affected-actors-cli.test.d.ts +2 -0
  145. package/dist/test/unit/bin/affected-actors-cli.test.d.ts.map +1 -0
  146. package/dist/test/unit/bin/affected-actors-cli.test.js +224 -0
  147. package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -0
  148. package/dist/test/unit/bin/affected-actors.test.d.ts +2 -0
  149. package/dist/test/unit/bin/affected-actors.test.d.ts.map +1 -0
  150. package/dist/test/unit/bin/affected-actors.test.js +153 -0
  151. package/dist/test/unit/bin/affected-actors.test.js.map +1 -0
  152. package/dist/test/unit/bin/build-cli.test.d.ts +2 -0
  153. package/dist/test/unit/bin/build-cli.test.d.ts.map +1 -0
  154. package/dist/test/unit/bin/build-cli.test.js +49 -0
  155. package/dist/test/unit/bin/build-cli.test.js.map +1 -0
  156. package/dist/test/unit/bin/build-from-local.test.js +12 -0
  157. package/dist/test/unit/bin/build-from-local.test.js.map +1 -1
  158. package/dist/test/unit/bin/build.test.js +116 -6
  159. package/dist/test/unit/bin/build.test.js.map +1 -1
  160. package/dist/test/unit/bin/compiled-cli.d.ts +6 -0
  161. package/dist/test/unit/bin/compiled-cli.d.ts.map +1 -0
  162. package/dist/test/unit/bin/compiled-cli.js +23 -0
  163. package/dist/test/unit/bin/compiled-cli.js.map +1 -0
  164. package/dist/test/unit/bin/diff-changes.test.js +168 -114
  165. package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
  166. package/dist/test/unit/bin/diff.test.js +25 -28
  167. package/dist/test/unit/bin/diff.test.js.map +1 -1
  168. package/dist/test/unit/bin/git.test.js +67 -288
  169. package/dist/test/unit/bin/git.test.js.map +1 -1
  170. package/dist/test/unit/bin/notify.test.d.ts +2 -0
  171. package/dist/test/unit/bin/notify.test.d.ts.map +1 -0
  172. package/dist/test/unit/bin/notify.test.js +222 -0
  173. package/dist/test/unit/bin/notify.test.js.map +1 -0
  174. package/dist/test/unit/bin/stdin-input.test.d.ts +2 -0
  175. package/dist/test/unit/bin/stdin-input.test.d.ts.map +1 -0
  176. package/dist/test/unit/bin/stdin-input.test.js +92 -0
  177. package/dist/test/unit/bin/stdin-input.test.js.map +1 -0
  178. package/dist/test/unit/bin/test-cli.test.d.ts +2 -0
  179. package/dist/test/unit/bin/test-cli.test.d.ts.map +1 -0
  180. package/dist/test/unit/bin/test-cli.test.js +146 -0
  181. package/dist/test/unit/bin/test-cli.test.js.map +1 -0
  182. package/dist/test/unit/bin/test-files-glob.test.d.ts +2 -0
  183. package/dist/test/unit/bin/test-files-glob.test.d.ts.map +1 -0
  184. package/dist/test/unit/bin/test-files-glob.test.js +102 -0
  185. package/dist/test/unit/bin/test-files-glob.test.js.map +1 -0
  186. package/dist/test/unit/bin/test-report.test.d.ts +2 -0
  187. package/dist/test/unit/bin/test-report.test.d.ts.map +1 -0
  188. package/dist/test/unit/bin/test-report.test.js +100 -0
  189. package/dist/test/unit/bin/test-report.test.js.map +1 -0
  190. package/dist/test/unit/bin/utils/config/load-config.test.js +19 -1
  191. package/dist/test/unit/bin/utils/config/load-config.test.js.map +1 -1
  192. package/dist/test/unit/bin/utils/config/parser.test.js +36 -4
  193. package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
  194. package/dist/test/unit/bin/utils/config/structures/grouped.test.js +6 -0
  195. package/dist/test/unit/bin/utils/config/structures/grouped.test.js.map +1 -1
  196. package/dist/test/unit/bin/utils/config/structures/legacy.test.js +1 -0
  197. package/dist/test/unit/bin/utils/config/structures/legacy.test.js.map +1 -1
  198. package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts +2 -0
  199. package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts.map +1 -0
  200. package/dist/test/unit/github-scripts/mask-config-secrets.test.js +50 -0
  201. package/dist/test/unit/github-scripts/mask-config-secrets.test.js.map +1 -0
  202. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts +2 -0
  203. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts.map +1 -0
  204. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js +102 -0
  205. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js.map +1 -0
  206. package/dist/test/unit/run-checks.test.d.ts +2 -0
  207. package/dist/test/unit/run-checks.test.d.ts.map +1 -0
  208. package/dist/test/unit/run-checks.test.js +62 -0
  209. package/dist/test/unit/run-checks.test.js.map +1 -0
  210. package/dist/test/unit/run-test-result.test.d.ts +2 -0
  211. package/dist/test/unit/run-test-result.test.d.ts.map +1 -0
  212. package/dist/test/unit/run-test-result.test.js +34 -0
  213. package/dist/test/unit/run-test-result.test.js.map +1 -0
  214. package/dist/test/unit/should-built-and-test.test.js +41 -38
  215. package/dist/test/unit/should-built-and-test.test.js.map +1 -1
  216. package/dist/tsconfig.tsbuildinfo +1 -1
  217. package/dist/workflow-scripts-src/run-with-apify-tokens.d.ts +3 -0
  218. package/dist/workflow-scripts-src/run-with-apify-tokens.d.ts.map +1 -0
  219. package/dist/workflow-scripts-src/run-with-apify-tokens.js +100 -0
  220. package/dist/workflow-scripts-src/run-with-apify-tokens.js.map +1 -0
  221. package/docs/cli.md +469 -0
  222. package/docs/lib.md +276 -0
  223. package/docs/workflows.md +455 -0
  224. package/eslint.config.mjs +10 -1
  225. package/lib/extend-expect.ts +48 -258
  226. package/lib/lib.ts +25 -34
  227. package/lib/run-checks.ts +101 -0
  228. package/lib/run-test-result.ts +45 -55
  229. package/package.json +4 -2
  230. package/platform-tests-investigate-and-fix/skills/assess-impact/SKILL.md +42 -0
  231. package/platform-tests-investigate-and-fix/skills/measure-flakiness/SKILL.md +4 -2
  232. package/test/bundle/cli-bundle.test.ts +97 -67
  233. package/test/bundle/token-wrapper.test.ts +110 -0
  234. package/test/e2e/cli-bundle.e2e.test.ts +60 -93
  235. package/test/e2e/git-build-release.e2e.test.ts +113 -0
  236. package/test/e2e/helpers.ts +82 -0
  237. package/test/unit/bin/actor-filtering.test.ts +8 -0
  238. package/test/unit/bin/actor-ids.test.ts +47 -0
  239. package/test/unit/bin/affected-actors-cli.test.ts +271 -0
  240. package/test/unit/bin/affected-actors.test.ts +221 -0
  241. package/test/unit/bin/build-cli.test.ts +61 -0
  242. package/test/unit/bin/build-from-local.test.ts +16 -0
  243. package/test/unit/bin/build.test.ts +149 -5
  244. package/test/unit/bin/compiled-cli.ts +23 -0
  245. package/test/unit/bin/diff-changes.test.ts +181 -114
  246. package/test/unit/bin/diff.test.ts +25 -28
  247. package/test/unit/bin/git.test.ts +76 -361
  248. package/test/unit/bin/notify.test.ts +299 -0
  249. package/test/unit/bin/stdin-input.test.ts +120 -0
  250. package/test/unit/bin/test-cli.test.ts +175 -0
  251. package/test/unit/bin/test-files-glob.test.ts +118 -0
  252. package/test/unit/bin/test-report.test.ts +118 -0
  253. package/test/unit/bin/utils/config/load-config.test.ts +26 -1
  254. package/test/unit/bin/utils/config/parser.test.ts +46 -5
  255. package/test/unit/bin/utils/config/structures/grouped.test.ts +12 -0
  256. package/test/unit/bin/utils/config/structures/legacy.test.ts +1 -0
  257. package/test/unit/github-scripts/mask-config-secrets.test.ts +61 -0
  258. package/test/unit/github-scripts/run-with-apify-tokens.test.ts +115 -0
  259. package/test/unit/run-checks.test.ts +76 -0
  260. package/test/unit/run-test-result.test.ts +44 -0
  261. package/test/unit/should-built-and-test.test.ts +46 -39
  262. package/tsconfig.json +1 -1
  263. package/vitest.config.ts +9 -0
  264. package/workflow-scripts-src/run-with-apify-tokens.ts +112 -0
  265. package/.github/scripts/check-major-tag-refs.mjs +0 -72
  266. package/dist/test/unit/bin/logging.test.d.ts +0 -2
  267. package/dist/test/unit/bin/logging.test.d.ts.map +0 -1
  268. package/dist/test/unit/bin/logging.test.js +0 -43
  269. package/dist/test/unit/bin/logging.test.js.map +0 -1
  270. package/dist/test/unit/bin/slack.test.d.ts +0 -2
  271. package/dist/test/unit/bin/slack.test.d.ts.map +0 -1
  272. package/dist/test/unit/bin/slack.test.js +0 -54
  273. package/dist/test/unit/bin/slack.test.js.map +0 -1
  274. package/test/unit/bin/logging.test.ts +0 -52
  275. package/test/unit/bin/slack.test.ts +0 -65
@@ -1,326 +1,24 @@
1
- import type { MockInstance } from 'vitest';
2
1
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
2
 
4
3
  import {
5
- getBranchOnlyChangedFiles,
6
- getChangedFiles,
7
- getCommits,
4
+ findCommitSha,
5
+ getChangedFilesBetween,
6
+ getCommitsBetween,
8
7
  getCurrentBranch,
9
- getReleaseChanges,
10
8
  getRepoName,
11
- hasMergeFromTarget,
12
9
  normalizeRepoUrl,
13
- parseBaseCommit,
10
+ resolveCommitSha,
14
11
  resolveReleaseBaseCommit,
15
12
  resolveRepoUrl,
16
13
  } from '../../../bin/git.js';
17
14
  import * as Utils from '../../../bin/utils.js';
18
15
 
19
- describe('getCommits', () => {
20
- const sourceBranch = 'feature-branch';
21
- const targetBranch = 'main';
22
-
23
- const sha1 = '1'.repeat(40);
24
- const sha2 = '2'.repeat(40);
25
- const sha3 = '3'.repeat(40);
26
-
27
- const commit1 = `${sha1}»¦«Author1»¦«Date1»¦«First Change On Feature`;
28
- const commit2 = `${sha2}»¦«Author1»¦«Date2»¦«Second Change On Feature`;
29
- const commit3 = `${sha3}»¦«Author1»¦«Date3»¦«Third Change On Feature`;
30
-
31
- let gitCommandSpy: MockInstance;
32
-
33
- beforeEach(() => {
34
- gitCommandSpy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue(`${commit3}\n${commit2}\n${commit1}`);
35
- });
36
-
37
- it('should return commits between source and target branches', async () => {
38
- // Act
39
- const commits = await getCommits({ sourceBranch, targetBranch });
40
-
41
- // Assert
42
- expect(commits).toStrictEqual([
43
- { sha: sha1, author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
44
- { sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
45
- { sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
46
- ]);
47
-
48
- expect(gitCommandSpy).toHaveBeenCalledTimes(1);
49
- expect(gitCommandSpy).toHaveBeenCalledWith(
50
- ['log', '--pretty=format:%H»¦«%aN<%aE>»¦«%aD»¦«%s', 'main..feature-branch'],
51
- { operation: 'list commits reachable from the source ref but absent from the target ref' },
52
- );
53
- });
54
-
55
- it('should return commits after the base commit if provided', async () => {
56
- // Act
57
- const commits = await getCommits({ sourceBranch, targetBranch, baseCommit: sha1 });
58
-
59
- // Assert
60
- expect(commits).toStrictEqual([
61
- { sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
62
- { sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
63
- ]);
64
-
65
- expect(gitCommandSpy).toHaveBeenCalledTimes(1);
66
- expect(gitCommandSpy).toHaveBeenCalledWith(
67
- ['log', '--pretty=format:%H»¦«%aN<%aE>»¦«%aD»¦«%s', 'main..feature-branch'],
68
- { operation: 'list commits reachable from the source ref but absent from the target ref' },
69
- );
70
- });
71
-
72
- it('should ignore the base commit and return all commits when it is the branch HEAD (rerun or force push)', async () => {
73
- // Act
74
- const commits = await getCommits({ sourceBranch, targetBranch, baseCommit: sha3 });
75
-
76
- // Assert
77
- expect(commits).toStrictEqual([
78
- { sha: sha1, author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
79
- { sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
80
- { sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
81
- ]);
82
- });
83
-
84
- it('should return all commits if base commit is not found', async () => {
85
- // Act
86
- const commits = await getCommits({ sourceBranch, targetBranch, baseCommit: 'a'.repeat(40) });
87
-
88
- // Assert
89
- expect(commits).toStrictEqual([
90
- { sha: sha1, author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
91
- { sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
92
- { sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
93
- ]);
94
-
95
- expect(gitCommandSpy).toHaveBeenCalledTimes(1);
96
- expect(gitCommandSpy).toHaveBeenCalledWith(
97
- ['log', '--pretty=format:%H»¦«%aN<%aE>»¦«%aD»¦«%s', 'main..feature-branch'],
98
- { operation: 'list commits reachable from the source ref but absent from the target ref' },
99
- );
100
- });
101
- });
102
-
103
- describe('getChangedFiles', () => {
104
- let gitCommandSpy: MockInstance;
105
-
106
- beforeEach(() => {
107
- gitCommandSpy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue('file1.txt\nfolder/file2.txt');
108
- });
109
-
110
- it('should return changed files between commits', async () => {
111
- // Arrange
112
- const firstSha = '1'.repeat(40);
113
- const lastSha = '3'.repeat(40);
114
- const commits = [
115
- { sha: firstSha, author: '', date: '', message: '' },
116
- { sha: lastSha, author: '', date: '', message: '' },
117
- ];
118
-
119
- // Act
120
- const changedFiles = await getChangedFiles(commits);
121
-
122
- // Assert
123
- expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);
124
-
125
- expect(gitCommandSpy).toHaveBeenCalledTimes(1);
126
- expect(gitCommandSpy).toHaveBeenCalledWith(['diff', '--name-only', `${firstSha}~..${lastSha}`], {
127
- operation: 'get the list of changed files between commits',
128
- });
129
- });
130
-
131
- it('should throw without running git when the commit list is empty', async () => {
132
- // Act & Assert
133
- await expect(getChangedFiles([])).rejects.toThrow('Cannot get changed files: the commit list is empty');
134
- expect(gitCommandSpy).not.toHaveBeenCalled();
135
- });
136
-
137
- it('should handle only one commit', async () => {
138
- // Arrange
139
- const onlySha = '1'.repeat(40);
140
- const commits = [{ sha: onlySha, author: '', date: '', message: '' }];
141
-
142
- // Act
143
- const changedFiles = await getChangedFiles(commits);
144
-
145
- // Assert
146
- expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);
147
-
148
- expect(gitCommandSpy).toHaveBeenCalledTimes(1);
149
- expect(gitCommandSpy).toHaveBeenCalledWith(['diff', '--name-only', `${onlySha}~..${onlySha}`], {
150
- operation: 'get the list of changed files between commits',
151
- });
152
- });
153
-
154
- it('should return an empty list when the net diff is empty (e.g. a commit and its revert)', async () => {
155
- // Arrange
156
- gitCommandSpy.mockResolvedValue('');
157
- const commits = [
158
- { sha: '1'.repeat(40), author: '', date: '', message: '' },
159
- { sha: '2'.repeat(40), author: '', date: '', message: '' },
160
- ];
161
-
162
- // Act
163
- const changedFiles = await getChangedFiles(commits);
164
-
165
- // Assert
166
- expect(changedFiles).toStrictEqual([]);
167
- });
168
- });
169
-
170
- describe('hasMergeFromTarget', () => {
171
- const sourceBranch = 'feature-branch';
172
- const targetBranch = 'main';
173
- const mergeSha = 'f'.repeat(40);
174
- const branchParentSha = 'b'.repeat(40);
175
- const targetParentSha = 't'.repeat(40);
176
- const differentMergeBase = '0'.repeat(40);
177
-
178
- let gitCommandSpy: MockInstance;
179
-
180
- beforeEach(() => {
181
- gitCommandSpy = vi.spyOn(Utils, 'runGitCommand');
182
- });
183
-
184
- it('should return false when there are no merge commits on the branch', async () => {
185
- gitCommandSpy.mockImplementation(async (args: string[]) => {
186
- if (args.includes('--merges')) return '';
187
- return '';
188
- });
189
-
190
- await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(false);
191
- expect(gitCommandSpy).toHaveBeenCalledWith(
192
- ['log', '--merges', '--pretty=format:%H', `${targetBranch}..${sourceBranch}`],
193
- { operation: 'find merge commits on the source branch that are absent from the target branch' },
194
- );
195
- });
196
-
197
- it('should return true when a merge commit has a parent reachable from targetBranch', async () => {
198
- gitCommandSpy.mockImplementation(async (args: string[]) => {
199
- if (args.includes('--merges')) return mergeSha;
200
- if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${targetParentSha}`;
201
- if (args[0] === 'merge-base' && args[1] === branchParentSha) return differentMergeBase;
202
- if (args[0] === 'merge-base' && args[1] === targetParentSha) return targetParentSha;
203
- return '';
204
- });
205
-
206
- await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(true);
207
- expect(gitCommandSpy).toHaveBeenCalledWith(['merge-base', targetParentSha, targetBranch], {
208
- operation: 'check whether a merge parent is reachable from the target branch',
209
- });
210
- });
211
-
212
- it('should return false when the merge commit parent is not reachable from targetBranch (unrelated branch merge)', async () => {
213
- const unrelatedSha = 'e'.repeat(40);
214
- gitCommandSpy.mockImplementation(async (args: string[]) => {
215
- if (args.includes('--merges')) return mergeSha;
216
- if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${unrelatedSha}`;
217
- // merge-base returns something other than the parent — not an ancestor
218
- if (args[0] === 'merge-base') return differentMergeBase;
219
- return '';
220
- });
221
-
222
- await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(false);
223
- });
224
-
225
- it('continues after merge-base reports unrelated histories', async () => {
226
- gitCommandSpy.mockImplementation(async (args: string[]) => {
227
- if (args.includes('--merges')) return mergeSha;
228
- if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${targetParentSha}`;
229
- if (args[0] === 'merge-base' && args[1] === branchParentSha) {
230
- throw new Utils.GitCommandError(args, Object.assign(new Error('no common ancestor'), { code: 1 }));
231
- }
232
- if (args[0] === 'merge-base' && args[1] === targetParentSha) return targetParentSha;
233
- return '';
234
- });
235
-
236
- await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(true);
237
- });
238
-
239
- it('propagates unexpected Git failures', async () => {
240
- gitCommandSpy.mockImplementation(
241
- async (args: string[], options?: Parameters<typeof Utils.runGitCommand>[1]) => {
242
- if (args.includes('--merges')) return mergeSha;
243
- if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${targetParentSha}`;
244
- throw new Utils.GitCommandError(
245
- args,
246
- Object.assign(new Error('Git failed'), { code: 128 }),
247
- options?.operation,
248
- );
249
- },
250
- );
251
-
252
- await expect(hasMergeFromTarget(sourceBranch, targetBranch)).rejects.toThrow(
253
- 'Failed to check whether a merge parent is reachable from the target branch.\n' +
254
- 'Command: git "merge-base"',
255
- );
256
- });
257
- });
258
-
259
- describe('getBranchOnlyChangedFiles', () => {
260
- const sourceBranch = 'feature-branch';
261
- const targetBranch = 'main';
262
-
263
- let gitCommandSpy: MockInstance;
264
-
265
- beforeEach(() => {
266
- gitCommandSpy = vi.spyOn(Utils, 'runGitCommand');
267
- });
268
-
269
- it('should return files touched by non-merge commits', async () => {
270
- gitCommandSpy.mockResolvedValue('README.md\n\nactors/foo_bar/src/main.ts\n');
271
-
272
- const result = await getBranchOnlyChangedFiles(sourceBranch, targetBranch);
273
-
274
- expect(result).toStrictEqual(['README.md', 'actors/foo_bar/src/main.ts']);
275
- expect(gitCommandSpy).toHaveBeenCalledWith(
276
- ['log', '--no-merges', '--name-only', '--pretty=format:', `${targetBranch}..${sourceBranch}`],
277
- { operation: 'list files changed by non-merge commits on the source branch' },
278
- );
279
- });
280
-
281
- it('should return empty array when there are no non-merge commits', async () => {
282
- gitCommandSpy.mockResolvedValue('');
283
-
284
- await expect(getBranchOnlyChangedFiles(sourceBranch, targetBranch)).resolves.toStrictEqual([]);
285
- });
286
- });
287
-
288
- const VALID_SHA = 'a'.repeat(40);
289
- const VALID_JSON = JSON.stringify({ sha: VALID_SHA, author: 'test', date: 'now', message: 'msg' });
290
-
291
- describe('parseBaseCommit', () => {
292
- it('should return undefined for undefined input', () => {
293
- expect(parseBaseCommit(undefined)).toBeUndefined();
294
- });
295
-
296
- it('should return undefined for empty string', () => {
297
- expect(parseBaseCommit('')).toBeUndefined();
298
- });
299
-
300
- it('should accept a plain SHA string', () => {
301
- expect(parseBaseCommit(VALID_SHA)).toBe(VALID_SHA);
302
- });
303
-
304
- it('should extract sha from a JSON commit object', () => {
305
- expect(parseBaseCommit(VALID_JSON)).toBe(VALID_SHA);
306
- });
307
-
308
- it('should throw on an invalid SHA string', () => {
309
- expect(() => parseBaseCommit('not-a-sha')).toThrow('Invalid base commit SHA');
310
- });
311
-
312
- it('should throw when JSON contains an invalid sha field', () => {
313
- const badJson = JSON.stringify({ sha: 'bad', author: 'test', date: 'now', message: 'msg' });
314
- expect(() => parseBaseCommit(badJson)).toThrow('Invalid base commit SHA');
315
- });
316
- });
317
-
318
16
  describe('getCurrentBranch', () => {
319
17
  it('should return the checked-out branch', async () => {
320
18
  const gitCommandSpy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue('master');
321
19
  await expect(getCurrentBranch()).resolves.toBe('master');
322
20
  expect(gitCommandSpy).toHaveBeenCalledWith(['rev-parse', '--abbrev-ref', 'HEAD'], {
323
- operation: 'determine the current branch for release',
21
+ operation: 'determine the checked-out branch',
324
22
  });
325
23
  });
326
24
 
@@ -393,7 +91,7 @@ describe('resolveReleaseBaseCommit', () => {
393
91
  });
394
92
  await expect(resolveReleaseBaseCommit(baseSha)).resolves.toBe(baseSha);
395
93
  expect(gitCommandSpy).toHaveBeenCalledWith(['rev-parse', '--verify', '--quiet', `${baseSha}^{commit}`], {
396
- operation: 'verify the release base commit in local Git history',
94
+ operation: `look up ${baseSha} in local Git history`,
397
95
  });
398
96
  expect(gitCommandSpy).toHaveBeenCalledWith(['merge-base', baseSha, 'HEAD'], {
399
97
  operation: 'check whether the release base commit is an ancestor of HEAD',
@@ -462,11 +160,11 @@ describe('resolveReleaseBaseCommit', () => {
462
160
  new Utils.GitCommandError(
463
161
  ['rev-parse', '--verify', '--quiet', `${baseSha}^{commit}`],
464
162
  Object.assign(new Error('Git failed'), { code: 128 }),
465
- 'verify the release base commit in local Git history',
163
+ `look up ${baseSha} in local Git history`,
466
164
  ),
467
165
  );
468
166
  await expect(resolveReleaseBaseCommit(baseSha)).rejects.toThrow(
469
- 'Failed to verify the release base commit in local Git history.\n' +
167
+ `Failed to look up ${baseSha} in local Git history.\n` +
470
168
  `Command: git "rev-parse" "--verify" "--quiet" "${baseSha}^{commit}"\nGit error: Git failed`,
471
169
  );
472
170
  });
@@ -474,71 +172,88 @@ describe('resolveReleaseBaseCommit', () => {
474
172
  it('should throw on an invalid SHA', async () => {
475
173
  await expect(resolveReleaseBaseCommit('not-a-sha')).rejects.toThrow('Invalid base commit SHA');
476
174
  });
175
+
176
+ it('checks ancestry against a custom head', async () => {
177
+ const gitCommandSpy = vi.spyOn(Utils, 'runGitCommand').mockImplementation(async (args: string[]) => {
178
+ if (args[0] === 'rev-parse') return baseSha;
179
+ if (args[0] === 'merge-base') return 'c'.repeat(40);
180
+ return '';
181
+ });
182
+ await expect(resolveReleaseBaseCommit(baseSha, 'origin/release')).rejects.toThrow(
183
+ 'is not an ancestor of origin/release',
184
+ );
185
+ expect(gitCommandSpy).toHaveBeenCalledWith(['merge-base', baseSha, 'origin/release'], {
186
+ operation: 'check whether the release base commit is an ancestor of origin/release',
187
+ });
188
+ });
477
189
  });
478
190
 
479
- describe('getReleaseChanges', () => {
480
- const baseSha = 'b'.repeat(40);
481
- const headSha = 'd'.repeat(40);
482
- const mergedBranchCommit = `${'1'.repeat(40)}»¦«Dev<dev@example.com>»¦«Date1»¦«feat: branch change`;
483
- const mergeCommit = `${headSha}»¦«Dev<dev@example.com>»¦«Date2»¦«Merge pull request #1`;
191
+ describe('getCommitsBetween', () => {
192
+ it('lists the commits after fromRef up to toRef, oldest first', async () => {
193
+ const older = `${'1'.repeat(40)}»¦«Dev<dev@example.com>»¦«Date1»¦«first`;
194
+ const newer = `${'2'.repeat(40)}»¦«Dev<dev@example.com>»¦«Date2»¦«second`;
195
+ const spy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue(`${newer}\n${older}`);
484
196
 
485
- let gitRunSpy: MockInstance;
197
+ const commits = await getCommitsBetween('base-sha', 'head-sha');
486
198
 
487
- beforeEach(() => {
488
- gitRunSpy = vi.spyOn(Utils, 'runGitCommand').mockImplementation(async (args: string[]) => {
489
- if (args[0] === 'rev-parse') return headSha;
490
- if (args[0] === 'log') return `${mergeCommit}\n${mergedBranchCommit}`;
491
- if (args.includes('--name-only')) return 'actors/foo/src/main.ts\nCHANGELOG.md';
492
- if (args[0] === 'diff')
493
- return 'diff --git a/CHANGELOG.md b/CHANGELOG.md\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -1 +1,2 @@\n+- Added foo\n # Changelog';
494
- return '';
199
+ expect(commits.map((c) => c.message)).toStrictEqual(['first', 'second']);
200
+ expect(spy).toHaveBeenCalledWith(['log', '--pretty=format:%H»¦«%aN<%aE>»¦«%aD»¦«%s', 'base-sha..head-sha'], {
201
+ operation: 'list commits reachable from the source ref but absent from the target ref',
495
202
  });
496
203
  });
204
+ });
205
+ describe('empty commit ranges', () => {
206
+ // Real git on this repository's own history: HEAD..HEAD has no commits
207
+ beforeEach(() => {
208
+ vi.restoreAllMocks();
209
+ });
497
210
 
498
- it('should diff the range directly and return the commits oldest first', async () => {
499
- const changes = await getReleaseChanges(baseSha);
211
+ it('getCommitsBetween returns no commits', async () => {
212
+ await expect(getCommitsBetween('HEAD', 'HEAD')).resolves.toStrictEqual([]);
213
+ });
214
+ });
500
215
 
501
- expect(changes).toStrictEqual({
502
- commits: [
503
- { sha: '1'.repeat(40), author: 'Dev<dev@example.com>', date: 'Date1', message: 'feat: branch change' },
504
- { sha: headSha, author: 'Dev<dev@example.com>', date: 'Date2', message: 'Merge pull request #1' },
505
- ],
506
- changedFiles: ['actors/foo/src/main.ts', 'CHANGELOG.md'],
507
- changelog: '- Added foo',
508
- });
509
- // Not from the parent of the oldest commit, which may predate the base commit for merge commits
510
- expect(gitRunSpy).toHaveBeenCalledWith(['rev-parse', 'HEAD'], {
511
- operation: 'identify the current release commit',
512
- });
513
- expect(gitRunSpy).toHaveBeenCalledWith(['diff', '--name-only', baseSha, 'HEAD'], {
514
- operation: 'list files changed since the last release',
515
- });
516
- expect(gitRunSpy).toHaveBeenCalledWith(
517
- ['log', '--pretty=format:%H»¦«%aN<%aE>»¦«%aD»¦«%s', `${baseSha}..HEAD`],
518
- { operation: 'list commits reachable from the source ref but absent from the target ref' },
519
- );
520
- expect(gitRunSpy).toHaveBeenCalledWith(['diff', baseSha, 'HEAD', '--', 'CHANGELOG.md'], {
521
- operation: 'read changelog changes since the last release',
216
+ describe('getChangedFilesBetween', () => {
217
+ it('diffs the two endpoints directly', async () => {
218
+ const spy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue('src/main.ts\nCHANGELOG.md');
219
+
220
+ await expect(getChangedFilesBetween('base-sha', 'head-sha')).resolves.toStrictEqual([
221
+ 'src/main.ts',
222
+ 'CHANGELOG.md',
223
+ ]);
224
+ expect(spy).toHaveBeenCalledWith(['diff', '--name-only', '--no-renames', 'base-sha..head-sha'], {
225
+ operation: 'get the list of changed files between commits',
522
226
  });
523
227
  });
228
+ });
524
229
 
525
- it('should return null when HEAD is the base commit', async () => {
526
- await expect(getReleaseChanges(headSha)).resolves.toBeNull();
527
- expect(gitRunSpy).toHaveBeenCalledOnce();
528
- expect(gitRunSpy).toHaveBeenCalledWith(['rev-parse', 'HEAD'], {
529
- operation: 'identify the current release commit',
230
+ describe('resolveCommitSha', () => {
231
+ it('resolves a ref to the commit it points to', async () => {
232
+ const sha = 'a'.repeat(40);
233
+ const spy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue(sha);
234
+ await expect(resolveCommitSha('origin/feature')).resolves.toBe(sha);
235
+ expect(spy).toHaveBeenCalledWith(['rev-parse', '--verify', 'origin/feature^{commit}'], {
236
+ operation: 'resolve origin/feature to a commit',
530
237
  });
531
238
  });
239
+ });
532
240
 
533
- it('skips the changelog diff when CHANGELOG.md did not change', async () => {
534
- gitRunSpy.mockImplementation(async (args: string[]) => {
535
- if (args[0] === 'rev-parse') return headSha;
536
- if (args[0] === 'log') return `${mergeCommit}\n${mergedBranchCommit}`;
537
- if (args.includes('--name-only')) return 'actors/foo/src/main.ts';
538
- return '';
539
- });
241
+ describe('findCommitSha', () => {
242
+ // Real git on this repository's own history, since what matters is how git answers
243
+ beforeEach(() => {
244
+ vi.restoreAllMocks();
245
+ });
246
+
247
+ it('resolves a ref in the local history to its commit', async () => {
248
+ await expect(findCommitSha('HEAD')).resolves.toMatch(/^[0-9a-f]{40}$/);
249
+ });
250
+
251
+ it('returns undefined for a commit missing from the local history', async () => {
252
+ await expect(findCommitSha('d'.repeat(40))).resolves.toBeUndefined();
253
+ });
540
254
 
541
- await expect(getReleaseChanges(baseSha)).resolves.toMatchObject({ changelog: null });
542
- expect(gitRunSpy).not.toHaveBeenCalledWith(['diff', baseSha, 'HEAD', '--', 'CHANGELOG.md']);
255
+ it('rethrows other git failures', async () => {
256
+ vi.spyOn(Utils, 'runGitCommand').mockRejectedValue(new Error('not a git repository'));
257
+ await expect(findCommitSha('HEAD')).rejects.toThrow('not a git repository');
543
258
  });
544
259
  });