apify-test-tools 0.6.0 → 0.6.1
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/workflows/_check_code.yaml +17 -19
- package/.github/workflows/_update_release_metadata.yaml +1 -1
- package/.github/workflows/manual_release_stable.yaml +1 -0
- package/.github/workflows/on_master.yaml +2 -0
- package/.husky/pre-commit +13 -0
- package/CHANGELOG.md +11 -3
- package/CONTRIBUTING.md +5 -0
- package/README.md +39 -43
- package/bin/build.ts +138 -87
- package/bin/diff-changes.ts +156 -0
- package/bin/diff-json-schema.ts +50 -0
- package/bin/git.ts +42 -11
- package/bin/github.ts +7 -6
- package/bin/main.ts +21 -11
- package/bin/slack.ts +6 -2
- package/bin/test-report.ts +55 -44
- package/bin/types.ts +56 -59
- package/bin/utils.ts +19 -131
- package/dist/bin/build.d.ts +1 -1
- package/dist/bin/build.d.ts.map +1 -1
- package/dist/bin/build.js +49 -31
- package/dist/bin/build.js.map +1 -1
- package/dist/bin/diff-changes.d.ts +16 -0
- package/dist/bin/diff-changes.d.ts.map +1 -0
- package/dist/bin/diff-changes.js +103 -0
- package/dist/bin/diff-changes.js.map +1 -0
- package/dist/bin/diff-json-schema.d.ts +7 -0
- package/dist/bin/diff-json-schema.d.ts.map +1 -0
- package/dist/bin/diff-json-schema.js +45 -0
- package/dist/bin/diff-json-schema.js.map +1 -0
- package/dist/bin/git.d.ts +8 -3
- package/dist/bin/git.d.ts.map +1 -1
- package/dist/bin/git.js +35 -10
- package/dist/bin/git.js.map +1 -1
- package/dist/bin/github.d.ts +1 -3
- package/dist/bin/github.d.ts.map +1 -1
- package/dist/bin/github.js +5 -5
- package/dist/bin/github.js.map +1 -1
- package/dist/bin/main.js +14 -5
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/slack.d.ts +1 -1
- package/dist/bin/slack.d.ts.map +1 -1
- package/dist/bin/slack.js +1 -1
- package/dist/bin/slack.js.map +1 -1
- package/dist/bin/test-report.d.ts.map +1 -1
- package/dist/bin/test-report.js +9 -3
- package/dist/bin/test-report.js.map +1 -1
- package/dist/bin/types.d.ts +0 -1
- package/dist/bin/types.d.ts.map +1 -1
- package/dist/bin/utils.d.ts +0 -14
- package/dist/bin/utils.d.ts.map +1 -1
- package/dist/bin/utils.js +12 -95
- package/dist/bin/utils.js.map +1 -1
- package/dist/lib/extend-expect.d.ts.map +1 -1
- package/dist/lib/extend-expect.js +11 -11
- package/dist/lib/extend-expect.js.map +1 -1
- package/dist/lib/lib.d.ts +3 -2
- package/dist/lib/lib.d.ts.map +1 -1
- package/dist/lib/lib.js +7 -3
- package/dist/lib/lib.js.map +1 -1
- package/dist/lib/run-test-result.d.ts +1 -1
- package/dist/lib/run-test-result.d.ts.map +1 -1
- package/dist/lib/run-test-result.js.map +1 -1
- package/dist/lib/types.d.ts +2 -3
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +4 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/test/unit/bin/diff-changes.test.d.ts +2 -0
- package/dist/test/unit/bin/diff-changes.test.d.ts.map +1 -0
- package/dist/test/unit/bin/diff-changes.test.js +159 -0
- package/dist/test/unit/bin/diff-changes.test.js.map +1 -0
- package/dist/test/unit/bin/diff.test.d.ts +2 -0
- package/dist/test/unit/bin/diff.test.d.ts.map +1 -0
- package/dist/test/unit/bin/diff.test.js +143 -0
- package/dist/test/unit/bin/diff.test.js.map +1 -0
- package/dist/test/unit/bin/git.test.js +48 -19
- package/dist/test/unit/bin/git.test.js.map +1 -1
- package/dist/test/unit/custom-matchers.test.js +3 -8
- package/dist/test/unit/custom-matchers.test.js.map +1 -1
- package/dist/test/unit/parse-commit.test.js +1 -1
- package/dist/test/unit/parse-commit.test.js.map +1 -1
- package/dist/test/unit/should-built-and-test.test.js +159 -39
- package/dist/test/unit/should-built-and-test.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/eslint.config.mjs +4 -2
- package/knip.json +1 -1
- package/lib/extend-expect.ts +21 -21
- package/lib/lib.ts +17 -17
- package/lib/run-test-result.ts +6 -2
- package/lib/types.ts +5 -8
- package/lib/utils.ts +13 -9
- package/package.json +10 -3
- package/test/tsconfig.json +2 -4
- package/test/unit/bin/diff-changes.test.ts +182 -0
- package/test/unit/bin/diff.test.ts +212 -0
- package/test/unit/bin/git.test.ts +59 -20
- package/test/unit/custom-matchers.test.ts +10 -9
- package/test/unit/parse-commit.test.ts +2 -1
- package/test/unit/should-built-and-test.test.ts +181 -40
- package/bin/consts.ts +0 -6
- package/dist/bin/consts.d.ts +0 -4
- package/dist/bin/consts.d.ts.map +0 -1
- package/dist/bin/consts.js +0 -6
- package/dist/bin/consts.js.map +0 -1
- package/example-github-events/pull-request.json +0 -523
- 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
package/lib/extend-expect.ts
CHANGED
|
@@ -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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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 <
|
|
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
|
|
package/lib/run-test-result.ts
CHANGED
|
@@ -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(
|
|
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 {
|
|
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
|
-
|
|
31
|
-
|
|
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) =>
|
|
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.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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"
|
package/test/tsconfig.json
CHANGED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { getChangedActors, maybeParseActorFolder } from '../../../bin/diff-changes.js';
|
|
4
|
+
import * as DiffJsonSchema from '../../../bin/diff-json-schema.js';
|
|
5
|
+
import type { ActorConfig } from '../../../bin/types.js';
|
|
6
|
+
|
|
7
|
+
const miniActor: ActorConfig = { actorName: 'foo/bar', folder: 'actors/foo_bar', isStandalone: false };
|
|
8
|
+
const standaloneActor: ActorConfig = {
|
|
9
|
+
actorName: 'standalone',
|
|
10
|
+
folder: 'standalone-actors/standalone',
|
|
11
|
+
isStandalone: true,
|
|
12
|
+
};
|
|
13
|
+
const actorConfigs = [miniActor, standaloneActor];
|
|
14
|
+
|
|
15
|
+
const commits = [{ sha: 'Commit1', author: '', date: '', message: '' }];
|
|
16
|
+
|
|
17
|
+
describe('maybeParseActorFolder', () => {
|
|
18
|
+
it('returns actorName for actors/ path', () => {
|
|
19
|
+
expect(maybeParseActorFolder('actors/foo_bar/actor.json')).toEqual({
|
|
20
|
+
isActorFolder: true,
|
|
21
|
+
actorName: 'foo/bar',
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns actorName for standalone-actors/ path', () => {
|
|
26
|
+
expect(maybeParseActorFolder('standalone-actors/my_actor/main.ts')).toEqual({
|
|
27
|
+
isActorFolder: true,
|
|
28
|
+
actorName: 'my/actor',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns false for top-level file', () => {
|
|
33
|
+
expect(maybeParseActorFolder('package.json')).toEqual({ isActorFolder: false });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns false for path with no file inside actor folder', () => {
|
|
37
|
+
expect(maybeParseActorFolder('actors/foo_bar')).toEqual({ isActorFolder: false });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns false for unrelated folder', () => {
|
|
41
|
+
expect(maybeParseActorFolder('src/utils.ts')).toEqual({ isActorFolder: false });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('getChangedActors', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('returns empty array when no files changed', () => {
|
|
51
|
+
expect(getChangedActors({ filepathsChanged: [], actorConfigs, commits })).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns empty array when only ignored top-level files changed', () => {
|
|
55
|
+
const result = getChangedActors({
|
|
56
|
+
filepathsChanged: ['.gitignore', 'README.md', '.husky/pre-commit', '.vscode/settings.json'],
|
|
57
|
+
actorConfigs,
|
|
58
|
+
commits,
|
|
59
|
+
});
|
|
60
|
+
expect(result).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns the actor when a functional file in its folder changes', () => {
|
|
64
|
+
const result = getChangedActors({
|
|
65
|
+
filepathsChanged: ['actors/foo_bar/src/main.ts'],
|
|
66
|
+
actorConfigs,
|
|
67
|
+
commits,
|
|
68
|
+
});
|
|
69
|
+
expect(result).toEqual([miniActor]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns actor when isLatest and README in actor folder changed (cosmetic)', () => {
|
|
73
|
+
const result = getChangedActors({
|
|
74
|
+
filepathsChanged: ['actors/foo_bar/README.md'],
|
|
75
|
+
actorConfigs,
|
|
76
|
+
commits,
|
|
77
|
+
isLatest: true,
|
|
78
|
+
});
|
|
79
|
+
expect(result).toEqual([miniActor]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('does not return actor when not isLatest and only README changed (cosmetic)', () => {
|
|
83
|
+
const result = getChangedActors({
|
|
84
|
+
filepathsChanged: ['actors/foo_bar/README.md'],
|
|
85
|
+
actorConfigs,
|
|
86
|
+
commits,
|
|
87
|
+
isLatest: false,
|
|
88
|
+
});
|
|
89
|
+
expect(result).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('returns actor when isLatest and JSON file has only cosmetic changes', () => {
|
|
93
|
+
vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(true);
|
|
94
|
+
const result = getChangedActors({
|
|
95
|
+
filepathsChanged: ['actors/foo_bar/actor.json'],
|
|
96
|
+
actorConfigs,
|
|
97
|
+
commits,
|
|
98
|
+
isLatest: true,
|
|
99
|
+
});
|
|
100
|
+
expect(result).toEqual([miniActor]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('does not return actor when not isLatest and JSON file has only cosmetic changes', () => {
|
|
104
|
+
vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(true);
|
|
105
|
+
const result = getChangedActors({
|
|
106
|
+
filepathsChanged: ['actors/foo_bar/actor.json'],
|
|
107
|
+
actorConfigs,
|
|
108
|
+
commits,
|
|
109
|
+
isLatest: false,
|
|
110
|
+
});
|
|
111
|
+
expect(result).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('returns actor when JSON file has functional changes', () => {
|
|
115
|
+
vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockReturnValue(false);
|
|
116
|
+
const result = getChangedActors({
|
|
117
|
+
filepathsChanged: ['actors/foo_bar/actor.json'],
|
|
118
|
+
actorConfigs,
|
|
119
|
+
commits,
|
|
120
|
+
});
|
|
121
|
+
expect(result).toEqual([miniActor]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('returns all non-standalone actors when a non-actor-folder functional file changes', () => {
|
|
125
|
+
const result = getChangedActors({
|
|
126
|
+
filepathsChanged: ['shared/utils.ts'],
|
|
127
|
+
actorConfigs,
|
|
128
|
+
commits,
|
|
129
|
+
});
|
|
130
|
+
expect(result).toContainEqual(miniActor);
|
|
131
|
+
expect(result).not.toContainEqual(standaloneActor);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('does not include standalone actor in all-actors expansion from changelog', () => {
|
|
135
|
+
const result = getChangedActors({
|
|
136
|
+
filepathsChanged: ['CHANGELOG.md'],
|
|
137
|
+
actorConfigs,
|
|
138
|
+
commits,
|
|
139
|
+
isLatest: true,
|
|
140
|
+
});
|
|
141
|
+
expect(result).toContainEqual(miniActor);
|
|
142
|
+
expect(result).not.toContainEqual(standaloneActor);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('includes standalone actor when its own folder changes', () => {
|
|
146
|
+
const result = getChangedActors({
|
|
147
|
+
filepathsChanged: ['standalone-actors/standalone/src/main.ts'],
|
|
148
|
+
actorConfigs,
|
|
149
|
+
commits,
|
|
150
|
+
});
|
|
151
|
+
expect(result).toContainEqual(standaloneActor);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('deduplicates actors when multiple files in same actor folder change', () => {
|
|
155
|
+
const result = getChangedActors({
|
|
156
|
+
filepathsChanged: ['actors/foo_bar/src/main.ts', 'actors/foo_bar/package.json'],
|
|
157
|
+
actorConfigs,
|
|
158
|
+
commits,
|
|
159
|
+
});
|
|
160
|
+
expect(result).toHaveLength(1);
|
|
161
|
+
expect(result).toContainEqual(miniActor);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('handles mixed changes: returns both mini and standalone actors', () => {
|
|
165
|
+
const result = getChangedActors({
|
|
166
|
+
filepathsChanged: ['actors/foo_bar/src/main.ts', 'standalone-actors/standalone/Dockerfile'],
|
|
167
|
+
actorConfigs,
|
|
168
|
+
commits,
|
|
169
|
+
});
|
|
170
|
+
expect(result).toContainEqual(miniActor);
|
|
171
|
+
expect(result).toContainEqual(standaloneActor);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('file paths are matched case-insensitively', () => {
|
|
175
|
+
const result = getChangedActors({
|
|
176
|
+
filepathsChanged: ['Actors/FOO_BAR/Main.ts'],
|
|
177
|
+
actorConfigs,
|
|
178
|
+
commits,
|
|
179
|
+
});
|
|
180
|
+
expect(result).toEqual([miniActor]);
|
|
181
|
+
});
|
|
182
|
+
});
|