apify-test-tools 0.5.7-beta.4 → 0.5.7-beta.6

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 (96) hide show
  1. package/.github/workflows/_check_code.yaml +17 -18
  2. package/.husky/pre-commit +13 -0
  3. package/CHANGELOG.md +4 -2
  4. package/CONTRIBUTING.md +5 -0
  5. package/README.md +39 -43
  6. package/bin/build.ts +138 -87
  7. package/bin/diff-changes.ts +52 -23
  8. package/bin/diff-json-schema.ts +10 -3
  9. package/bin/git.ts +31 -9
  10. package/bin/github.ts +7 -6
  11. package/bin/main.ts +14 -8
  12. package/bin/slack.ts +6 -2
  13. package/bin/test-report.ts +55 -44
  14. package/bin/types.ts +56 -59
  15. package/bin/utils.ts +17 -12
  16. package/dist/bin/build.d.ts +1 -1
  17. package/dist/bin/build.d.ts.map +1 -1
  18. package/dist/bin/build.js +49 -31
  19. package/dist/bin/build.js.map +1 -1
  20. package/dist/bin/diff-changes.d.ts +1 -1
  21. package/dist/bin/diff-changes.d.ts.map +1 -1
  22. package/dist/bin/diff-changes.js +15 -3
  23. package/dist/bin/diff-changes.js.map +1 -1
  24. package/dist/bin/diff-json-schema.d.ts.map +1 -1
  25. package/dist/bin/diff-json-schema.js +6 -1
  26. package/dist/bin/diff-json-schema.js.map +1 -1
  27. package/dist/bin/git.d.ts +7 -2
  28. package/dist/bin/git.d.ts.map +1 -1
  29. package/dist/bin/git.js +23 -6
  30. package/dist/bin/git.js.map +1 -1
  31. package/dist/bin/github.d.ts +1 -3
  32. package/dist/bin/github.d.ts.map +1 -1
  33. package/dist/bin/github.js +5 -5
  34. package/dist/bin/github.js.map +1 -1
  35. package/dist/bin/main.js +7 -1
  36. package/dist/bin/main.js.map +1 -1
  37. package/dist/bin/slack.d.ts +1 -1
  38. package/dist/bin/slack.d.ts.map +1 -1
  39. package/dist/bin/slack.js +1 -1
  40. package/dist/bin/slack.js.map +1 -1
  41. package/dist/bin/test-report.d.ts.map +1 -1
  42. package/dist/bin/test-report.js +9 -3
  43. package/dist/bin/test-report.js.map +1 -1
  44. package/dist/bin/types.d.ts +0 -1
  45. package/dist/bin/types.d.ts.map +1 -1
  46. package/dist/bin/utils.d.ts.map +1 -1
  47. package/dist/bin/utils.js +11 -7
  48. package/dist/bin/utils.js.map +1 -1
  49. package/dist/lib/extend-expect.d.ts.map +1 -1
  50. package/dist/lib/extend-expect.js +11 -11
  51. package/dist/lib/extend-expect.js.map +1 -1
  52. package/dist/lib/lib.d.ts +3 -2
  53. package/dist/lib/lib.d.ts.map +1 -1
  54. package/dist/lib/lib.js +7 -3
  55. package/dist/lib/lib.js.map +1 -1
  56. package/dist/lib/run-test-result.d.ts +1 -1
  57. package/dist/lib/run-test-result.d.ts.map +1 -1
  58. package/dist/lib/run-test-result.js.map +1 -1
  59. package/dist/lib/types.d.ts +2 -3
  60. package/dist/lib/types.d.ts.map +1 -1
  61. package/dist/lib/utils.d.ts +1 -1
  62. package/dist/lib/utils.d.ts.map +1 -1
  63. package/dist/lib/utils.js +4 -1
  64. package/dist/lib/utils.js.map +1 -1
  65. package/dist/test/unit/bin/diff-changes.test.js +14 -7
  66. package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
  67. package/dist/test/unit/bin/diff.test.js +14 -4
  68. package/dist/test/unit/bin/diff.test.js.map +1 -1
  69. package/dist/test/unit/bin/git.test.js +48 -19
  70. package/dist/test/unit/bin/git.test.js.map +1 -1
  71. package/dist/test/unit/custom-matchers.test.js +3 -8
  72. package/dist/test/unit/custom-matchers.test.js.map +1 -1
  73. package/dist/test/unit/should-built-and-test.test.js +75 -18
  74. package/dist/test/unit/should-built-and-test.test.js.map +1 -1
  75. package/dist/tsconfig.tsbuildinfo +1 -1
  76. package/eslint.config.mjs +4 -2
  77. package/knip.json +1 -1
  78. package/lib/extend-expect.ts +21 -21
  79. package/lib/lib.ts +17 -17
  80. package/lib/run-test-result.ts +6 -2
  81. package/lib/types.ts +5 -8
  82. package/lib/utils.ts +13 -9
  83. package/package.json +10 -3
  84. package/test/tsconfig.json +2 -4
  85. package/test/unit/bin/diff-changes.test.ts +14 -7
  86. package/test/unit/bin/diff.test.ts +19 -21
  87. package/test/unit/bin/git.test.ts +59 -20
  88. package/test/unit/custom-matchers.test.ts +10 -9
  89. package/test/unit/should-built-and-test.test.ts +77 -20
  90. package/bin/consts.ts +0 -6
  91. package/dist/bin/consts.d.ts +0 -4
  92. package/dist/bin/consts.d.ts.map +0 -1
  93. package/dist/bin/consts.js +0 -6
  94. package/dist/bin/consts.js.map +0 -1
  95. package/example-github-events/pull-request.json +0 -523
  96. package/example-github-events/push.json +0 -216
package/eslint.config.mjs CHANGED
@@ -6,7 +6,7 @@ import tsEslint from 'typescript-eslint';
6
6
 
7
7
  // eslint-disable-next-line import/no-default-export
8
8
  export default [
9
- { ignores: ['**/dist', 'eslint.config.mjs'] },
9
+ { ignores: ['**/dist', 'eslint.config.mjs', '.github'] },
10
10
  ...apify,
11
11
  prettier,
12
12
  {
@@ -25,6 +25,8 @@ export default [
25
25
  },
26
26
  rules: {
27
27
  'no-console': 0,
28
+ // This was used heavily, I don't have string opinion so turning it off for now, feel free to refactor later
29
+ 'no-use-before-define': 'off',
28
30
  },
29
31
  },
30
- ];
32
+ ];
package/knip.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/knip@5/schema.json"
3
- }
3
+ }
@@ -1,8 +1,8 @@
1
1
  import type { Assertion, ExpectStatic } from 'vitest';
2
2
 
3
3
  import { TO_FINISH_WITH_OPTIONS } from './consts.js';
4
+ import type { RunTestResult } from './run-test-result.js';
4
5
  import type { Interval, ToFinishWithOptions } from './types.js';
5
- import { RunTestResult } from './run-test-result.js';
6
6
 
7
7
  export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
8
8
  expect.extend({
@@ -110,7 +110,7 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
110
110
  },
111
111
  toFinishWith: async <PpeEvent extends string>(
112
112
  received: RunTestResult,
113
- userOptions: ToFinishWithOptions<PpeEvent>
113
+ userOptions: ToFinishWithOptions<PpeEvent>,
114
114
  ) => {
115
115
  const options = {
116
116
  ...TO_FINISH_WITH_OPTIONS,
@@ -146,12 +146,12 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
146
146
  const checkInterval = (
147
147
  value: number | undefined,
148
148
  key: ['datasetItemCount', 'duration', 'failedRequests', 'requestsRetries'][number],
149
- label: string
149
+ label: string,
150
150
  ) => {
151
151
  const result = isWithinInterval(diffs, value, options, key);
152
152
  if (result === false) {
153
153
  failedAssertions.push(
154
- `Failed ${label} check, expected ${JSON.stringify(options[key])}, got ${value}.`
154
+ `Failed ${label} check, expected ${JSON.stringify(options[key])}, got ${value}.`,
155
155
  );
156
156
  }
157
157
  };
@@ -161,17 +161,15 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
161
161
  checkInterval(stats?.requestsFailed, 'failedRequests', 'failed requests');
162
162
  checkInterval(stats?.requestsRetries, 'requestsRetries', 'requests retries');
163
163
 
164
- {
165
- if (options.maxRetriesPerRequest !== null) {
166
- const maxRetriesPerRequestObserved = (stats?.requestRetryHistogram ?? [0]).length - 1;
167
- if (maxRetriesPerRequestObserved > options.maxRetriesPerRequest) {
168
- diffs.pass = false;
169
- diffs.actual.push(`maxRetriesPerRequest=${maxRetriesPerRequestObserved}`);
170
- diffs.expected.push(`maxRetriesPerRequest<=${options.maxRetriesPerRequest}`);
171
- failedAssertions.push(
172
- `Failed max retries observed check, expected <=${options.maxRetriesPerRequest}, got ${maxRetriesPerRequestObserved}.`
173
- );
174
- }
164
+ if (options.maxRetriesPerRequest !== null) {
165
+ const maxRetriesPerRequestObserved = (stats?.requestRetryHistogram ?? [0]).length - 1;
166
+ if (maxRetriesPerRequestObserved > options.maxRetriesPerRequest) {
167
+ diffs.pass = false;
168
+ diffs.actual.push(`maxRetriesPerRequest=${maxRetriesPerRequestObserved}`);
169
+ diffs.expected.push(`maxRetriesPerRequest<=${options.maxRetriesPerRequest}`);
170
+ failedAssertions.push(
171
+ `Failed max retries observed check, expected <=${options.maxRetriesPerRequest}, got ${maxRetriesPerRequestObserved}.`,
172
+ );
175
173
  }
176
174
  }
177
175
  const ppeDiffs: Diffs = {
@@ -208,8 +206,8 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
208
206
  diffs.expected.push(ppeDiffs.expected.join('\n '));
209
207
  failedAssertions.push(
210
208
  `Failed PPE event counts check, expected ${JSON.stringify(
211
- options.chargedEventCounts
212
- )}, got ${JSON.stringify(chargedEventCounts)}.`
209
+ options.chargedEventCounts,
210
+ )}, got ${JSON.stringify(chargedEventCounts)}.`,
213
211
  );
214
212
  }
215
213
  }
@@ -228,7 +226,7 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
228
226
  diffs.actual.push(` logs=[${occuredLogs.join(', ')}]`);
229
227
  diffs.expected.push(` logs=[]`);
230
228
  failedAssertions.push(
231
- `Failed forbidden logs check, expected [] but got [${occuredLogs.join(', ')}].`
229
+ `Failed forbidden logs check, expected [] but got [${occuredLogs.join(', ')}].`,
232
230
  );
233
231
  }
234
232
  }
@@ -280,15 +278,16 @@ const isWithinInterval = <T extends string>(
280
278
  diffs: Diffs,
281
279
  actual: number | undefined,
282
280
  options: Record<T, Interval | null>,
283
- intervalOption: T
281
+ intervalOption: T,
284
282
  ) => {
285
283
  const expected = options[intervalOption];
286
284
  if (expected === null) {
287
285
  // check is disabled if expected value is null
288
- return;
286
+ return undefined;
289
287
  }
290
288
  if (typeof expected === 'number') {
291
289
  if (actual !== expected) {
290
+ // eslint-disable-next-line no-param-reassign --- Don't want to touch this code :)
292
291
  diffs.pass = false;
293
292
  diffs.actual.push(`${intervalOption}=${actual}`);
294
293
  diffs.expected.push(`${intervalOption}=${expected}`);
@@ -297,11 +296,12 @@ const isWithinInterval = <T extends string>(
297
296
  } else if (typeof expected === 'object') {
298
297
  const { min, max } = expected;
299
298
  if (actual === undefined || (min !== undefined && actual < min) || (max !== undefined && actual > max)) {
299
+ // eslint-disable-next-line no-param-reassign
300
300
  diffs.pass = false;
301
301
  diffs.actual.push(`${intervalOption}=${actual}`);
302
302
  diffs.expected.push(`${intervalOption}=<${min ?? ''},${max ?? ''}>`);
303
303
  return false;
304
304
  }
305
305
  }
306
- return;
306
+ return undefined;
307
307
  };
package/lib/lib.ts CHANGED
@@ -1,15 +1,11 @@
1
- import { Actor, ActorRun, ActorRunListItem, ActorStandby, ApifyClient, Task } from 'apify-client';
2
- import {
3
- describe as vitestDescribe,
4
- ExpectStatic,
5
- TestFunction,
6
- test as vitestTest,
7
- SuiteFactory,
8
- TestContext,
9
- } from 'vitest';
10
- import type { ActorBuild, ActorTestOptions, RunOptions } from './types.js';
11
- import { RunTestResult } from './run-test-result.js';
1
+ import type { Actor, ActorRun, ActorRunListItem, ActorStandby, Task } from 'apify-client';
2
+ import { ApifyClient } from 'apify-client';
3
+ import type { SuiteFactory, TestContext, TestFunction } from 'vitest';
4
+ import { describe as vitestDescribe, ExpectStatic, test as vitestTest } from 'vitest';
5
+
12
6
  import { extendExpect } from './extend-expect.js';
7
+ import { RunTestResult } from './run-test-result.js';
8
+ import type { ActorBuild, ActorTestOptions, RunOptions } from './types.js';
13
9
  import { getActorPrefilledInput, sleep } from './utils.js';
14
10
 
15
11
  const ACTOR_BUILDS = 'ACTOR_BUILDS';
@@ -56,7 +52,7 @@ export const testActor = <T>(
56
52
  actorName: string,
57
53
  testName: string,
58
54
  fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>,
59
- testOptions?: ActorTestOptions
55
+ testOptions?: ActorTestOptions,
60
56
  ) => {
61
57
  const options = {
62
58
  ...DEFAULT_TEST_ACTOR_OPTIONS,
@@ -64,7 +60,7 @@ export const testActor = <T>(
64
60
  };
65
61
  const name = `${actorName}: ${testName}`;
66
62
  const shouldRun = !!RUN_ALL_PLATFORM_TESTS || config.has(actorName);
67
- vitestTest.runIf(shouldRun)(name, options, async <T extends TestContext>(context: T) => {
63
+ vitestTest.runIf(shouldRun)(name, options, async <TYPE extends TestContext>(context: TYPE) => {
68
64
  const { expect, ...rest } = context;
69
65
  await fn({
70
66
  expect: extendExpect(expect),
@@ -80,11 +76,12 @@ export const testActor = <T>(
80
76
  *
81
77
  * Using task is just current shortcoming of standby feature but ideally we would use Actor directly
82
78
  */
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
80
  export const testStandbyActor = <I = any, O = any>(
84
81
  actorName: string,
85
82
  testName: string,
86
83
  fn: TestFunction<{ callStandby: ReturnType<typeof createStartStandbyFn<I, O>> }>,
87
- testOptions?: ActorTestOptions
84
+ testOptions?: ActorTestOptions,
88
85
  ) => {
89
86
  const options = {
90
87
  ...DEFAULT_TEST_ACTOR_OPTIONS,
@@ -105,7 +102,9 @@ export const testStandbyActor = <I = any, O = any>(
105
102
  callStandby: createStartStandbyFn(standbyTask),
106
103
  ...rest,
107
104
  });
108
- } catch {}
105
+ } catch {
106
+ /* */
107
+ }
109
108
 
110
109
  const { taskId } = standbyTask;
111
110
  const runs = (await apifyClient.task(taskId).runs().list()).items;
@@ -122,13 +121,14 @@ export const testStandbyActor = <I = any, O = any>(
122
121
 
123
122
  export const testTestActor = <T>(
124
123
  testName: string,
125
- fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>
124
+ fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>,
126
125
  ) => {
127
126
  vitestTest(testName, async (context) => {
128
127
  const { expect, ...rest } = context;
129
128
  await fn({
130
129
  expect: extendExpect(expect),
131
130
  // @ts-expect-error: this just to test custom matchers
131
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
132
132
  run: () => {},
133
133
  ...rest,
134
134
  });
@@ -191,7 +191,7 @@ const createStandbyTask = async (actorNameOrId: string, buildNumber?: string): P
191
191
  throw new Error(`Actor "${actorNameOrId} doesn't contain actorStandby options`);
192
192
  }
193
193
  const { isEnabled, ...defaultActorStandby } = actorInfo.actorStandby;
194
- delete defaultActorStandby.disableStandbyFieldsOverride
194
+ delete defaultActorStandby.disableStandbyFieldsOverride;
195
195
 
196
196
  const build = buildNumber ?? defaultActorStandby.build;
197
197
 
@@ -1,4 +1,5 @@
1
- import { ActorRun, ApifyClient, KeyValueStoreClient } from 'apify-client';
1
+ import type { ActorRun, ApifyClient, KeyValueStoreClient } from 'apify-client';
2
+
2
3
  import type { Dataset, SdkCrawlerStatistics } from './types.js';
3
4
 
4
5
  export class RunTestResult {
@@ -9,7 +10,10 @@ export class RunTestResult {
9
10
  private runInfo: ActorRun | undefined;
10
11
  private input: unknown | undefined;
11
12
 
12
- constructor(private readonly apifyClient: ApifyClient, private readonly run: ActorRun) {
13
+ constructor(
14
+ private readonly apifyClient: ApifyClient,
15
+ private readonly run: ActorRun,
16
+ ) {
13
17
  /**/
14
18
  }
15
19
 
package/lib/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ActorCallOptions } from 'apify-client';
2
- import { Assertion, TestOptions } from 'vitest';
1
+ import type { ActorCallOptions } from 'apify-client';
2
+ import type { Assertion, TestOptions } from 'vitest';
3
3
 
4
4
  export type ActorBuild = {
5
5
  buildId: string;
@@ -50,10 +50,6 @@ export type ToFinishWithOptionsWithDefaults = {
50
50
  forbiddenLogs: string[];
51
51
  maxRetriesPerRequest: number | null;
52
52
  };
53
- export type IntervalOption<PpeEvent extends string> = keyof Pick<
54
- ToFinishWithOptions<PpeEvent>,
55
- 'datasetItemCount' | 'requestsRetries' | 'failedRequests' | 'duration'
56
- >;
57
53
 
58
54
  export type ToFinishWithOptions<PpeEvent extends string> = Partial<ToFinishWithOptionsWithDefaults> & {
59
55
  datasetItemCount: Interval;
@@ -144,9 +140,10 @@ export type ActorTestOptions = Omit<TestOptions, 'retry'> & {
144
140
  };
145
141
 
146
142
  declare module 'vitest' {
147
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
148
144
  interface Assertion<T = any> extends ActorMatchers<T> {}
149
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
150
146
  interface Matchers<T = any> extends ActorMatchers<T> {}
147
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
151
148
  interface AsymmetricMatchersContaining extends ActorMatchers {}
152
149
  }
package/lib/utils.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { ActorRun, ApifyClient } from 'apify-client';
2
- import { TestContext } from 'vitest';
1
+ import type { ApifyClient } from 'apify-client';
3
2
 
4
3
  /**
5
4
  * Gets prefilled values for a provided build or, if not provided, uses actor's
@@ -16,26 +15,29 @@ export const getActorPrefilledInput = async (
16
15
  const defaultBuildTag = actorInfo?.defaultRunOptions.build;
17
16
 
18
17
  const taggedBuild = actorInfo?.taggedBuilds?.[defaultBuildTag || ''];
18
+ // eslint-disable-next-line no-param-reassign --- I think here it is cleaner than creating dummy variable
19
19
  buildId = taggedBuild?.buildId;
20
20
 
21
21
  if (!buildId) {
22
22
  console.error(`Coudn't find default build for actor ${actorNameOrId}. Prefilled values will not be used.`);
23
- return {}
23
+ return {};
24
24
  }
25
25
  }
26
26
 
27
27
  const buildInfo = await apifyClient.build(buildId).get();
28
28
 
29
- const inputSchema = buildInfo?.actorDefinition?.input as {
30
- properties: Record<string, { prefill?: unknown }>
31
- } | undefined
29
+ const inputSchema = buildInfo?.actorDefinition?.input as
30
+ | {
31
+ properties: Record<string, { prefill?: unknown }>;
32
+ }
33
+ | undefined;
32
34
 
33
35
  if (!inputSchema) {
34
36
  console.error(
35
37
  `Coudn't find input schema definition for actor ${actorNameOrId}, build ${buildId}.`,
36
38
  'Prefilled values will not be used',
37
39
  );
38
- return {}
40
+ return {};
39
41
  }
40
42
 
41
43
  const prefill: Record<string, unknown> = {};
@@ -50,5 +52,7 @@ export const getActorPrefilledInput = async (
50
52
  };
51
53
 
52
54
  export const sleep = async (ms: number) => {
53
- await new Promise((resolve) => setTimeout(resolve, ms));
54
- }
55
+ await new Promise((resolve) => {
56
+ setTimeout(resolve, ms);
57
+ });
58
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.5.7-beta.4",
3
+ "version": "0.5.7-beta.6",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "repository": {
@@ -34,7 +34,9 @@
34
34
  "typescript": "^5.9.3",
35
35
  "typescript-eslint": "^8.34.1",
36
36
  "vitest": "^3.2.4",
37
- "knip": "^5.65.0"
37
+ "knip": "^5.65.0",
38
+ "husky": "^9.0.11",
39
+ "lint-staged": "^15.2.2"
38
40
  },
39
41
  "peerDependencies": {
40
42
  "vitest": ">=3.2.4"
@@ -49,7 +51,12 @@
49
51
  "format:check": "prettier --check .",
50
52
  "type-check": "tsc --noEmit",
51
53
  "test": "vitest run",
52
- "check-unused": "npx knip"
54
+ "check-unused": "npx knip",
55
+ "prepare": "husky .husky || true"
56
+ },
57
+ "lint-staged": {
58
+ "*.js": "eslint",
59
+ "*.ts": "eslint"
53
60
  },
54
61
  "author": "oklinov",
55
62
  "license": "ISC"
@@ -7,9 +7,7 @@
7
7
  "noUnusedLocals": false,
8
8
  "noEmit": true,
9
9
  "lib": ["DOM", "es2021"],
10
- "skipLibCheck": true,
10
+ "skipLibCheck": true
11
11
  },
12
- "include": [
13
- "./**/*",
14
- ]
12
+ "include": ["./**/*"]
15
13
  }
@@ -5,18 +5,28 @@ import * as DiffJsonSchema from '../../../bin/diff-json-schema.js';
5
5
  import type { ActorConfig } from '../../../bin/types.js';
6
6
 
7
7
  const miniActor: ActorConfig = { actorName: 'foo/bar', folder: 'actors/foo_bar', isStandalone: false };
8
- const standaloneActor: ActorConfig = { actorName: 'standalone', folder: 'standalone-actors/standalone', isStandalone: true };
8
+ const standaloneActor: ActorConfig = {
9
+ actorName: 'standalone',
10
+ folder: 'standalone-actors/standalone',
11
+ isStandalone: true,
12
+ };
9
13
  const actorConfigs = [miniActor, standaloneActor];
10
14
 
11
15
  const commits = [{ sha: 'Commit1', author: '', date: '', message: '' }];
12
16
 
13
17
  describe('maybeParseActorFolder', () => {
14
18
  it('returns actorName for actors/ path', () => {
15
- expect(maybeParseActorFolder('actors/foo_bar/actor.json')).toEqual({ isActorFolder: true, actorName: 'foo/bar' });
19
+ expect(maybeParseActorFolder('actors/foo_bar/actor.json')).toEqual({
20
+ isActorFolder: true,
21
+ actorName: 'foo/bar',
22
+ });
16
23
  });
17
24
 
18
25
  it('returns actorName for standalone-actors/ path', () => {
19
- expect(maybeParseActorFolder('standalone-actors/my_actor/main.ts')).toEqual({ isActorFolder: true, actorName: 'my/actor' });
26
+ expect(maybeParseActorFolder('standalone-actors/my_actor/main.ts')).toEqual({
27
+ isActorFolder: true,
28
+ actorName: 'my/actor',
29
+ });
20
30
  });
21
31
 
22
32
  it('returns false for top-level file', () => {
@@ -153,10 +163,7 @@ describe('getChangedActors', () => {
153
163
 
154
164
  it('handles mixed changes: returns both mini and standalone actors', () => {
155
165
  const result = getChangedActors({
156
- filepathsChanged: [
157
- 'actors/foo_bar/src/main.ts',
158
- 'standalone-actors/standalone/Dockerfile',
159
- ],
166
+ filepathsChanged: ['actors/foo_bar/src/main.ts', 'standalone-actors/standalone/Dockerfile'],
160
167
  actorConfigs,
161
168
  commits,
162
169
  });
@@ -17,9 +17,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
17
17
  });
18
18
 
19
19
  const mockGitCalls = (oldJson: string, newJson: string) => {
20
- gitCommandSpy
21
- .mockReturnValueOnce(oldJson)
22
- .mockReturnValueOnce(newJson);
20
+ gitCommandSpy.mockReturnValueOnce(oldJson).mockReturnValueOnce(newJson);
23
21
  };
24
22
 
25
23
  test('returns true when nothing changed', () => {
@@ -69,10 +67,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
69
67
  });
70
68
 
71
69
  test('returns true when only sectionDescription changes', () => {
72
- mockGitCalls(
73
- JSON.stringify({ sectionDescription: 'Old' }),
74
- JSON.stringify({ sectionDescription: 'New' }),
75
- );
70
+ mockGitCalls(JSON.stringify({ sectionDescription: 'Old' }), JSON.stringify({ sectionDescription: 'New' }));
76
71
  expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
77
72
  });
78
73
 
@@ -85,18 +80,12 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
85
80
  });
86
81
 
87
82
  test('returns false when a functional field is added', () => {
88
- mockGitCalls(
89
- JSON.stringify({ title: 'My field' }),
90
- JSON.stringify({ title: 'My field', type: 'string' }),
91
- );
83
+ mockGitCalls(JSON.stringify({ title: 'My field' }), JSON.stringify({ title: 'My field', type: 'string' }));
92
84
  expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
93
85
  });
94
86
 
95
87
  test('returns false when a functional field is removed', () => {
96
- mockGitCalls(
97
- JSON.stringify({ type: 'string', title: 'My field' }),
98
- JSON.stringify({ title: 'My field' }),
99
- );
88
+ mockGitCalls(JSON.stringify({ type: 'string', title: 'My field' }), JSON.stringify({ title: 'My field' }));
100
89
  expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
101
90
  });
102
91
 
@@ -159,10 +148,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
159
148
  });
160
149
 
161
150
  test('returns false when array content changes (not under a non-functional key)', () => {
162
- mockGitCalls(
163
- JSON.stringify({ enum: [1, 2, 3] }),
164
- JSON.stringify({ enum: [1, 2, 4] }),
165
- );
151
+ mockGitCalls(JSON.stringify({ enum: [1, 2, 3] }), JSON.stringify({ enum: [1, 2, 4] }));
166
152
  expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(false);
167
153
  });
168
154
 
@@ -176,8 +162,20 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
176
162
 
177
163
  test('returns true for a realistic actor.json with only title/description change', () => {
178
164
  mockGitCalls(
179
- JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'Old Title', description: 'Old description', version: '1.0' }),
180
- JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'New Title', description: 'New description', version: '1.0' }),
165
+ JSON.stringify({
166
+ actorSpecification: 1,
167
+ name: 'my-actor',
168
+ title: 'Old Title',
169
+ description: 'Old description',
170
+ version: '1.0',
171
+ }),
172
+ JSON.stringify({
173
+ actorSpecification: 1,
174
+ name: 'my-actor',
175
+ title: 'New Title',
176
+ description: 'New description',
177
+ version: '1.0',
178
+ }),
181
179
  );
182
180
  expect(isCosmeticOnlyJsonSchemaChange(commits, 'actors/foo/actor.json')).toBe(true);
183
181
  });
@@ -1,14 +1,20 @@
1
- import { beforeEach, describe, expect, it, MockInstance, vi } from 'vitest';
2
- import { getChangedFiles, getCommits } from '../../../bin/git.js';
1
+ import type { MockInstance } from 'vitest';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { getChangedFiles, getCommits, parseBaseCommit } from '../../../bin/git.js';
3
5
  import * as Utils from '../../../bin/utils.js';
4
6
 
5
7
  describe('getCommits', () => {
6
8
  const sourceBranch = 'feature-branch';
7
9
  const targetBranch = 'main';
8
10
 
9
- const commit1 = 'Commit1»¦«Author1»¦«Date1»¦«First Change On Feature';
10
- const commit2 = 'Commit2»¦«Author1»¦«Date2»¦«Second Change On Feature';
11
- const commit3 = 'Commit3»¦«Author1»¦«Date3»¦«Third Change On Feature';
11
+ const sha1 = '1'.repeat(40);
12
+ const sha2 = '2'.repeat(40);
13
+ const sha3 = '3'.repeat(40);
14
+
15
+ const commit1 = `${sha1}»¦«Author1»¦«Date1»¦«First Change On Feature`;
16
+ const commit2 = `${sha2}»¦«Author1»¦«Date2»¦«Second Change On Feature`;
17
+ const commit3 = `${sha3}»¦«Author1»¦«Date3»¦«Third Change On Feature`;
12
18
 
13
19
  let gitCommandSpy: MockInstance;
14
20
 
@@ -24,9 +30,9 @@ describe('getCommits', () => {
24
30
 
25
31
  // Assert
26
32
  expect(commits).toStrictEqual([
27
- { sha: 'Commit1', author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
28
- { sha: 'Commit2', author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
29
- { sha: 'Commit3', author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
33
+ { sha: sha1, author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
34
+ { sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
35
+ { sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
30
36
  ]);
31
37
 
32
38
  expect(gitCommandSpy).toHaveBeenCalledTimes(1);
@@ -37,12 +43,12 @@ describe('getCommits', () => {
37
43
 
38
44
  it('should return commits after the base commit if provided', () => {
39
45
  // Act
40
- const commits = getCommits({ sourceBranch, targetBranch, baseCommit: 'Commit1' });
46
+ const commits = getCommits({ sourceBranch, targetBranch, baseCommit: sha1 });
41
47
 
42
48
  // Assert
43
49
  expect(commits).toStrictEqual([
44
- { sha: 'Commit2', author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
45
- { sha: 'Commit3', author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
50
+ { sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
51
+ { sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
46
52
  ]);
47
53
 
48
54
  expect(gitCommandSpy).toHaveBeenCalledTimes(1);
@@ -53,13 +59,13 @@ describe('getCommits', () => {
53
59
 
54
60
  it('should return all commits if base commit is not found', () => {
55
61
  // Act
56
- const commits = getCommits({ sourceBranch, targetBranch, baseCommit: 'NonExistingCommit' });
62
+ const commits = getCommits({ sourceBranch, targetBranch, baseCommit: 'a'.repeat(40) });
57
63
 
58
64
  // Assert
59
65
  expect(commits).toStrictEqual([
60
- { sha: 'Commit1', author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
61
- { sha: 'Commit2', author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
62
- { sha: 'Commit3', author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
66
+ { sha: sha1, author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
67
+ { sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
68
+ { sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
63
69
  ]);
64
70
 
65
71
  expect(gitCommandSpy).toHaveBeenCalledTimes(1);
@@ -78,9 +84,11 @@ describe('getChangedFiles', () => {
78
84
 
79
85
  it('should return changed files between commits', () => {
80
86
  // Arrange
87
+ const firstSha = '1'.repeat(40);
88
+ const lastSha = '3'.repeat(40);
81
89
  const commits = [
82
- { sha: 'Commit1', author: '', date: '', message: '' },
83
- { sha: 'Commit3', author: '', date: '', message: '' },
90
+ { sha: firstSha, author: '', date: '', message: '' },
91
+ { sha: lastSha, author: '', date: '', message: '' },
84
92
  ];
85
93
 
86
94
  // Act
@@ -90,12 +98,13 @@ describe('getChangedFiles', () => {
90
98
  expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);
91
99
 
92
100
  expect(gitCommandSpy).toHaveBeenCalledTimes(1);
93
- expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only Commit1~..Commit3`);
101
+ expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only ${firstSha}~..${lastSha}`);
94
102
  });
95
103
 
96
104
  it('should handle only one commit', () => {
97
105
  // Arrange
98
- const commits = [{ sha: 'Commit1', author: '', date: '', message: '' }];
106
+ const onlySha = '1'.repeat(40);
107
+ const commits = [{ sha: onlySha, author: '', date: '', message: '' }];
99
108
 
100
109
  // Act
101
110
  const changedFiles = getChangedFiles(commits);
@@ -104,6 +113,36 @@ describe('getChangedFiles', () => {
104
113
  expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);
105
114
 
106
115
  expect(gitCommandSpy).toHaveBeenCalledTimes(1);
107
- expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only Commit1~..Commit1`);
116
+ expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only ${onlySha}~..${onlySha}`);
117
+ });
118
+ });
119
+
120
+ const VALID_SHA = 'a'.repeat(40);
121
+ const VALID_JSON = JSON.stringify({ sha: VALID_SHA, author: 'test', date: 'now', message: 'msg' });
122
+
123
+ describe('parseBaseCommit', () => {
124
+ it('should return undefined for undefined input', () => {
125
+ expect(parseBaseCommit(undefined)).toBeUndefined();
126
+ });
127
+
128
+ it('should return undefined for empty string', () => {
129
+ expect(parseBaseCommit('')).toBeUndefined();
130
+ });
131
+
132
+ it('should accept a plain SHA string', () => {
133
+ expect(parseBaseCommit(VALID_SHA)).toBe(VALID_SHA);
134
+ });
135
+
136
+ it('should extract sha from a JSON commit object', () => {
137
+ expect(parseBaseCommit(VALID_JSON)).toBe(VALID_SHA);
138
+ });
139
+
140
+ it('should throw on an invalid SHA string', () => {
141
+ expect(() => parseBaseCommit('not-a-sha')).toThrow('Invalid base commit SHA');
142
+ });
143
+
144
+ it('should throw when JSON contains an invalid sha field', () => {
145
+ const badJson = JSON.stringify({ sha: 'bad', author: 'test', date: 'now', message: 'msg' });
146
+ expect(() => parseBaseCommit(badJson)).toThrow('Invalid base commit SHA');
108
147
  });
109
148
  });