apify-test-tools 0.10.2-beta.2 → 0.10.2-beta.21
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/.gitattributes +1 -0
- package/.github/actionlint.yaml +11 -0
- package/.github/actions/checkout-restore-dependencies/action.yaml +58 -6
- package/.github/cli/apify-test-tools.mjs +135 -142
- package/.github/scripts/mask-config-secrets.sh +17 -0
- package/.github/scripts/run-with-apify-tokens.mjs +63 -103
- package/.github/workflows/_check_code.yaml +5 -21
- package/.github/workflows/_move_major_tag.yaml +2 -2
- package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +89 -8
- package/.github/workflows/public_platform-tests.yaml +68 -30
- package/.github/workflows/public_pr-build-test.yaml +77 -38
- package/.github/workflows/public_push-build-latest.yaml +49 -11
- package/.github/workflows/public_review.yaml +5 -4
- package/.github/workflows/public_stale-issues.yaml +51 -0
- package/.husky/pre-commit +4 -4
- package/.prettierignore +2 -1
- package/CHANGELOG.md +23 -0
- package/CONTRIBUTING.md +117 -17
- package/README.md +221 -595
- package/bin/actor-ids.ts +41 -0
- package/bin/affected-actors.ts +211 -0
- package/bin/build-from-local.ts +19 -1
- package/bin/build.ts +105 -39
- package/bin/diff-changes.ts +69 -36
- package/bin/diff-json-schema.ts +5 -6
- package/bin/git.ts +73 -175
- package/bin/main.ts +142 -189
- package/bin/notify.ts +198 -0
- package/bin/run-tests.ts +182 -0
- package/bin/slack.ts +16 -76
- package/bin/stdin-input.ts +156 -0
- package/bin/test-files-glob.ts +126 -0
- package/bin/test-report.ts +141 -140
- package/bin/types.ts +4 -7
- package/bin/utils/config/load-config.ts +20 -2
- package/bin/utils/config/parser.ts +40 -8
- package/bin/utils/config/structures/grouped.ts +2 -2
- package/bin/utils/config/structures/legacy.ts +1 -1
- package/dist/bin/actor-ids.d.ts +15 -0
- package/dist/bin/actor-ids.d.ts.map +1 -0
- package/dist/bin/actor-ids.js +30 -0
- package/dist/bin/actor-ids.js.map +1 -0
- package/dist/bin/affected-actors.d.ts +55 -0
- package/dist/bin/affected-actors.d.ts.map +1 -0
- package/dist/bin/affected-actors.js +142 -0
- package/dist/bin/affected-actors.js.map +1 -0
- package/dist/bin/build-from-local.d.ts +2 -1
- package/dist/bin/build-from-local.d.ts.map +1 -1
- package/dist/bin/build-from-local.js +6 -2
- package/dist/bin/build-from-local.js.map +1 -1
- package/dist/bin/build.d.ts +18 -5
- package/dist/bin/build.d.ts.map +1 -1
- package/dist/bin/build.js +74 -24
- package/dist/bin/build.js.map +1 -1
- package/dist/bin/diff-changes.d.ts +21 -5
- package/dist/bin/diff-changes.d.ts.map +1 -1
- package/dist/bin/diff-changes.js +41 -28
- package/dist/bin/diff-changes.js.map +1 -1
- package/dist/bin/diff-json-schema.d.ts +2 -2
- package/dist/bin/diff-json-schema.d.ts.map +1 -1
- package/dist/bin/diff-json-schema.js +1 -4
- package/dist/bin/diff-json-schema.js.map +1 -1
- package/dist/bin/git.d.ts +26 -34
- package/dist/bin/git.d.ts.map +1 -1
- package/dist/bin/git.js +60 -143
- package/dist/bin/git.js.map +1 -1
- package/dist/bin/main.js +126 -140
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/notify.d.ts +35 -0
- package/dist/bin/notify.d.ts.map +1 -0
- package/dist/bin/notify.js +131 -0
- package/dist/bin/notify.js.map +1 -0
- package/dist/bin/run-tests.d.ts +21 -0
- package/dist/bin/run-tests.d.ts.map +1 -0
- package/dist/bin/run-tests.js +149 -0
- package/dist/bin/run-tests.js.map +1 -0
- package/dist/bin/slack.d.ts +2 -14
- package/dist/bin/slack.d.ts.map +1 -1
- package/dist/bin/slack.js +17 -46
- package/dist/bin/slack.js.map +1 -1
- package/dist/bin/stdin-input.d.ts +79 -0
- package/dist/bin/stdin-input.d.ts.map +1 -0
- package/dist/bin/stdin-input.js +124 -0
- package/dist/bin/stdin-input.js.map +1 -0
- package/dist/bin/test-files-glob.d.ts +18 -0
- package/dist/bin/test-files-glob.d.ts.map +1 -0
- package/dist/bin/test-files-glob.js +108 -0
- package/dist/bin/test-files-glob.js.map +1 -0
- package/dist/bin/test-report.d.ts +69 -8
- package/dist/bin/test-report.d.ts.map +1 -1
- package/dist/bin/test-report.js +76 -67
- package/dist/bin/test-report.js.map +1 -1
- package/dist/bin/types.d.ts +4 -7
- package/dist/bin/types.d.ts.map +1 -1
- package/dist/bin/utils/config/load-config.d.ts +8 -0
- package/dist/bin/utils/config/load-config.d.ts.map +1 -1
- package/dist/bin/utils/config/load-config.js +14 -1
- package/dist/bin/utils/config/load-config.js.map +1 -1
- package/dist/bin/utils/config/parser.d.ts +15 -2
- package/dist/bin/utils/config/parser.d.ts.map +1 -1
- package/dist/bin/utils/config/parser.js +29 -8
- package/dist/bin/utils/config/parser.js.map +1 -1
- package/dist/bin/utils/config/structures/grouped.js +2 -2
- package/dist/bin/utils/config/structures/grouped.js.map +1 -1
- package/dist/bin/utils/config/structures/legacy.js +1 -1
- package/dist/bin/utils/config/structures/legacy.js.map +1 -1
- package/dist/lib/extend-expect.d.ts.map +1 -1
- package/dist/lib/extend-expect.js +37 -218
- package/dist/lib/extend-expect.js.map +1 -1
- package/dist/lib/lib.d.ts +1 -5
- package/dist/lib/lib.d.ts.map +1 -1
- package/dist/lib/lib.js +19 -31
- package/dist/lib/lib.js.map +1 -1
- package/dist/lib/run-checks.d.ts +14 -0
- package/dist/lib/run-checks.d.ts.map +1 -0
- package/dist/lib/run-checks.js +82 -0
- package/dist/lib/run-checks.js.map +1 -0
- package/dist/lib/run-test-result.d.ts +3 -6
- package/dist/lib/run-test-result.d.ts.map +1 -1
- package/dist/lib/run-test-result.js +41 -54
- package/dist/lib/run-test-result.js.map +1 -1
- package/dist/test/bundle/cli-bundle.test.js +76 -56
- package/dist/test/bundle/cli-bundle.test.js.map +1 -1
- package/dist/test/bundle/token-wrapper.test.d.ts +2 -0
- package/dist/test/bundle/token-wrapper.test.d.ts.map +1 -0
- package/dist/test/bundle/token-wrapper.test.js +80 -0
- package/dist/test/bundle/token-wrapper.test.js.map +1 -0
- package/dist/test/e2e/cli-bundle.e2e.test.js +33 -68
- package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
- package/dist/test/e2e/git-build-release.e2e.test.d.ts +2 -0
- package/dist/test/e2e/git-build-release.e2e.test.d.ts.map +1 -0
- package/dist/test/e2e/git-build-release.e2e.test.js +87 -0
- package/dist/test/e2e/git-build-release.e2e.test.js.map +1 -0
- package/dist/test/e2e/helpers.d.ts +37 -0
- package/dist/test/e2e/helpers.d.ts.map +1 -0
- package/dist/test/e2e/helpers.js +57 -0
- package/dist/test/e2e/helpers.js.map +1 -0
- package/dist/test/unit/bin/actor-filtering.test.js +7 -0
- package/dist/test/unit/bin/actor-filtering.test.js.map +1 -1
- package/dist/test/unit/bin/actor-ids.test.d.ts +2 -0
- package/dist/test/unit/bin/actor-ids.test.d.ts.map +1 -0
- package/dist/test/unit/bin/actor-ids.test.js +40 -0
- package/dist/test/unit/bin/actor-ids.test.js.map +1 -0
- package/dist/test/unit/bin/affected-actors-cli.test.d.ts +2 -0
- package/dist/test/unit/bin/affected-actors-cli.test.d.ts.map +1 -0
- package/dist/test/unit/bin/affected-actors-cli.test.js +224 -0
- package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -0
- package/dist/test/unit/bin/affected-actors.test.d.ts +2 -0
- package/dist/test/unit/bin/affected-actors.test.d.ts.map +1 -0
- package/dist/test/unit/bin/affected-actors.test.js +153 -0
- package/dist/test/unit/bin/affected-actors.test.js.map +1 -0
- package/dist/test/unit/bin/build-cli.test.d.ts +2 -0
- package/dist/test/unit/bin/build-cli.test.d.ts.map +1 -0
- package/dist/test/unit/bin/build-cli.test.js +49 -0
- package/dist/test/unit/bin/build-cli.test.js.map +1 -0
- package/dist/test/unit/bin/build-from-local.test.js +12 -0
- package/dist/test/unit/bin/build-from-local.test.js.map +1 -1
- package/dist/test/unit/bin/build.test.js +116 -6
- package/dist/test/unit/bin/build.test.js.map +1 -1
- package/dist/test/unit/bin/compiled-cli.d.ts +6 -0
- package/dist/test/unit/bin/compiled-cli.d.ts.map +1 -0
- package/dist/test/unit/bin/compiled-cli.js +23 -0
- package/dist/test/unit/bin/compiled-cli.js.map +1 -0
- package/dist/test/unit/bin/diff-changes.test.js +168 -114
- package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
- package/dist/test/unit/bin/diff.test.js +25 -28
- package/dist/test/unit/bin/diff.test.js.map +1 -1
- package/dist/test/unit/bin/git.test.js +67 -288
- package/dist/test/unit/bin/git.test.js.map +1 -1
- package/dist/test/unit/bin/notify.test.d.ts +2 -0
- package/dist/test/unit/bin/notify.test.d.ts.map +1 -0
- package/dist/test/unit/bin/notify.test.js +222 -0
- package/dist/test/unit/bin/notify.test.js.map +1 -0
- package/dist/test/unit/bin/stdin-input.test.d.ts +2 -0
- package/dist/test/unit/bin/stdin-input.test.d.ts.map +1 -0
- package/dist/test/unit/bin/stdin-input.test.js +92 -0
- package/dist/test/unit/bin/stdin-input.test.js.map +1 -0
- package/dist/test/unit/bin/test-cli.test.d.ts +2 -0
- package/dist/test/unit/bin/test-cli.test.d.ts.map +1 -0
- package/dist/test/unit/bin/test-cli.test.js +165 -0
- package/dist/test/unit/bin/test-cli.test.js.map +1 -0
- package/dist/test/unit/bin/test-files-glob.test.d.ts +2 -0
- package/dist/test/unit/bin/test-files-glob.test.d.ts.map +1 -0
- package/dist/test/unit/bin/test-files-glob.test.js +102 -0
- package/dist/test/unit/bin/test-files-glob.test.js.map +1 -0
- package/dist/test/unit/bin/test-report.test.d.ts +2 -0
- package/dist/test/unit/bin/test-report.test.d.ts.map +1 -0
- package/dist/test/unit/bin/test-report.test.js +100 -0
- package/dist/test/unit/bin/test-report.test.js.map +1 -0
- package/dist/test/unit/bin/utils/config/load-config.test.js +19 -1
- package/dist/test/unit/bin/utils/config/load-config.test.js.map +1 -1
- package/dist/test/unit/bin/utils/config/parser.test.js +36 -4
- package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
- package/dist/test/unit/bin/utils/config/structures/grouped.test.js +6 -0
- package/dist/test/unit/bin/utils/config/structures/grouped.test.js.map +1 -1
- package/dist/test/unit/bin/utils/config/structures/legacy.test.js +1 -0
- package/dist/test/unit/bin/utils/config/structures/legacy.test.js.map +1 -1
- package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts +2 -0
- package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts.map +1 -0
- package/dist/test/unit/github-scripts/mask-config-secrets.test.js +50 -0
- package/dist/test/unit/github-scripts/mask-config-secrets.test.js.map +1 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts +2 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts.map +1 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js +102 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js.map +1 -0
- package/dist/test/unit/run-checks.test.d.ts +2 -0
- package/dist/test/unit/run-checks.test.d.ts.map +1 -0
- package/dist/test/unit/run-checks.test.js +62 -0
- package/dist/test/unit/run-checks.test.js.map +1 -0
- package/dist/test/unit/run-test-result.test.d.ts +2 -0
- package/dist/test/unit/run-test-result.test.d.ts.map +1 -0
- package/dist/test/unit/run-test-result.test.js +34 -0
- package/dist/test/unit/run-test-result.test.js.map +1 -0
- package/dist/test/unit/should-built-and-test.test.js +41 -38
- package/dist/test/unit/should-built-and-test.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/workflow-scripts-src/run-with-apify-tokens.d.ts +3 -0
- package/dist/workflow-scripts-src/run-with-apify-tokens.d.ts.map +1 -0
- package/dist/workflow-scripts-src/run-with-apify-tokens.js +100 -0
- package/dist/workflow-scripts-src/run-with-apify-tokens.js.map +1 -0
- package/docs/cli.md +473 -0
- package/docs/lib.md +276 -0
- package/docs/workflows.md +455 -0
- package/eslint.config.mjs +10 -1
- package/lib/extend-expect.ts +48 -258
- package/lib/lib.ts +25 -34
- package/lib/run-checks.ts +101 -0
- package/lib/run-test-result.ts +45 -55
- package/package.json +4 -2
- package/platform-tests-investigate-and-fix/skills/assess-impact/SKILL.md +42 -0
- package/platform-tests-investigate-and-fix/skills/measure-flakiness/SKILL.md +4 -2
- package/test/bundle/cli-bundle.test.ts +97 -67
- package/test/bundle/token-wrapper.test.ts +110 -0
- package/test/e2e/cli-bundle.e2e.test.ts +60 -93
- package/test/e2e/git-build-release.e2e.test.ts +113 -0
- package/test/e2e/helpers.ts +82 -0
- package/test/unit/bin/actor-filtering.test.ts +8 -0
- package/test/unit/bin/actor-ids.test.ts +47 -0
- package/test/unit/bin/affected-actors-cli.test.ts +271 -0
- package/test/unit/bin/affected-actors.test.ts +221 -0
- package/test/unit/bin/build-cli.test.ts +61 -0
- package/test/unit/bin/build-from-local.test.ts +16 -0
- package/test/unit/bin/build.test.ts +149 -5
- package/test/unit/bin/compiled-cli.ts +23 -0
- package/test/unit/bin/diff-changes.test.ts +181 -114
- package/test/unit/bin/diff.test.ts +25 -28
- package/test/unit/bin/git.test.ts +76 -361
- package/test/unit/bin/notify.test.ts +299 -0
- package/test/unit/bin/stdin-input.test.ts +120 -0
- package/test/unit/bin/test-cli.test.ts +207 -0
- package/test/unit/bin/test-files-glob.test.ts +118 -0
- package/test/unit/bin/test-report.test.ts +118 -0
- package/test/unit/bin/utils/config/load-config.test.ts +26 -1
- package/test/unit/bin/utils/config/parser.test.ts +46 -5
- package/test/unit/bin/utils/config/structures/grouped.test.ts +12 -0
- package/test/unit/bin/utils/config/structures/legacy.test.ts +1 -0
- package/test/unit/github-scripts/mask-config-secrets.test.ts +61 -0
- package/test/unit/github-scripts/run-with-apify-tokens.test.ts +115 -0
- package/test/unit/run-checks.test.ts +76 -0
- package/test/unit/run-test-result.test.ts +44 -0
- package/test/unit/should-built-and-test.test.ts +46 -39
- package/tsconfig.json +1 -1
- package/vitest.config.ts +9 -0
- package/workflow-scripts-src/run-with-apify-tokens.ts +112 -0
- package/.github/scripts/check-major-tag-refs.mjs +0 -72
- package/dist/test/unit/bin/logging.test.d.ts +0 -2
- package/dist/test/unit/bin/logging.test.d.ts.map +0 -1
- package/dist/test/unit/bin/logging.test.js +0 -43
- package/dist/test/unit/bin/logging.test.js.map +0 -1
- package/dist/test/unit/bin/slack.test.d.ts +0 -2
- package/dist/test/unit/bin/slack.test.d.ts.map +0 -1
- package/dist/test/unit/bin/slack.test.js +0 -54
- package/dist/test/unit/bin/slack.test.js.map +0 -1
- package/test/unit/bin/logging.test.ts +0 -52
- package/test/unit/bin/slack.test.ts +0 -65
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { createTestFilesMatcher, resolveTestFilesGlob } from '../../../bin/test-files-glob.js';
|
|
8
|
+
|
|
9
|
+
describe('createTestFilesMatcher', () => {
|
|
10
|
+
it('matches repo-root-relative paths, ignoring case and including dot files', () => {
|
|
11
|
+
const isMatch = createTestFilesMatcher('test/platform/{daily,hourly}/**');
|
|
12
|
+
expect(isMatch('test/platform/daily/a.test.ts')).toBe(true);
|
|
13
|
+
expect(isMatch('Test/Platform/Hourly/.b.test.ts')).toBe(true);
|
|
14
|
+
expect(isMatch('test/platform/daily-without-pr/a.test.ts')).toBe(false);
|
|
15
|
+
expect(isMatch('test/platform/utils.ts')).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('matches nothing without a glob', () => {
|
|
19
|
+
expect(createTestFilesMatcher(undefined)('test/platform/a.test.ts')).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('rejects an absolute glob', () => {
|
|
23
|
+
expect(() => createTestFilesMatcher('/repo/test/platform/**')).toThrow('must be relative to the repo root');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('resolveTestFilesGlob', () => {
|
|
28
|
+
let repoDir: string;
|
|
29
|
+
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
repoDir = await mkdtemp(path.join(os.tmpdir(), 'apify-test-files-glob-'));
|
|
32
|
+
for (const file of [
|
|
33
|
+
'test/platform/utils.ts',
|
|
34
|
+
'test/platform/daily/a.test.ts',
|
|
35
|
+
'test/platform/daily-without-pr/b.test.ts',
|
|
36
|
+
'test/platform/hourly/nested/c.test.ts',
|
|
37
|
+
'test/platform/node_modules/d.test.ts',
|
|
38
|
+
'other/test/platform/daily/e.test.ts',
|
|
39
|
+
]) {
|
|
40
|
+
await mkdir(path.join(repoDir, path.dirname(file)), { recursive: true });
|
|
41
|
+
await writeFile(path.join(repoDir, file), '');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterAll(async () => {
|
|
46
|
+
if (repoDir) await rm(repoDir, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const files = async (glob: string) => (await resolveTestFilesGlob(glob, repoDir)).files;
|
|
50
|
+
const paths = async (glob: string) => (await resolveTestFilesGlob(glob, repoDir)).paths;
|
|
51
|
+
|
|
52
|
+
it('lists the matched files from the repo root, sorted, skipping node_modules', async () => {
|
|
53
|
+
expect(await files('test/platform/**')).toEqual([
|
|
54
|
+
'test/platform/daily-without-pr/b.test.ts',
|
|
55
|
+
'test/platform/daily/a.test.ts',
|
|
56
|
+
'test/platform/hourly/nested/c.test.ts',
|
|
57
|
+
'test/platform/utils.ts',
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('selects folders with a brace pattern', async () => {
|
|
62
|
+
expect(await files('test/platform/{daily,hourly}/**')).toEqual([
|
|
63
|
+
'test/platform/daily/a.test.ts',
|
|
64
|
+
'test/platform/hourly/nested/c.test.ts',
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('accepts a leading ./', async () => {
|
|
69
|
+
expect(await files('./test/platform/daily/**')).toEqual(['test/platform/daily/a.test.ts']);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('walks the whole repo when the glob has no static base', async () => {
|
|
73
|
+
expect(await files('**/daily/*.test.ts')).toEqual([
|
|
74
|
+
'other/test/platform/daily/e.test.ts',
|
|
75
|
+
'test/platform/daily/a.test.ts',
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('resolves a glob without wildcards to the file itself', async () => {
|
|
80
|
+
expect(await resolveTestFilesGlob('test/platform/utils.ts', repoDir)).toStrictEqual({
|
|
81
|
+
files: ['test/platform/utils.ts'],
|
|
82
|
+
paths: ['test/platform/utils.ts'],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('matches no file with a glob relative to test/platform or a folder without **', async () => {
|
|
87
|
+
expect(await files('daily/**')).toEqual([]);
|
|
88
|
+
expect(await files('test/platform/daily')).toEqual([]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('gives a folder whose files all match as one path, ending with / so it is not a prefix of a sibling', async () => {
|
|
92
|
+
expect(await paths('test/platform/{daily,hourly}/**')).toEqual([
|
|
93
|
+
'test/platform/daily/',
|
|
94
|
+
'test/platform/hourly/',
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('lists the matched files one by one in a folder that also holds files the glob leaves out', async () => {
|
|
99
|
+
// test/platform/utils.ts is left out, so test/platform/ can't be one path
|
|
100
|
+
expect(await paths('test/platform/**/*.test.ts')).toEqual([
|
|
101
|
+
'test/platform/daily-without-pr/',
|
|
102
|
+
'test/platform/daily/',
|
|
103
|
+
'test/platform/hourly/',
|
|
104
|
+
]);
|
|
105
|
+
// other/ holds only the matched file, so it is the highest folder that can be one path
|
|
106
|
+
expect(await paths('**/daily/*.test.ts')).toEqual(['other/', 'test/platform/daily/']);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('never gives a folder that holds a skipped node_modules as one path', async () => {
|
|
110
|
+
// test/platform/node_modules/ is not walked, so test/platform/ might hold files the glob leaves out
|
|
111
|
+
expect(await paths('test/platform/**')).toEqual([
|
|
112
|
+
'test/platform/daily-without-pr/',
|
|
113
|
+
'test/platform/daily/',
|
|
114
|
+
'test/platform/hourly/',
|
|
115
|
+
'test/platform/utils.ts',
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { toTestReport, type VitestJsonReport } from '../../../bin/test-report.js';
|
|
4
|
+
|
|
5
|
+
const cwd = '/repo';
|
|
6
|
+
const run = { actorId: 'owner/a', runId: 'run1', runLink: 'https://console.apify.com/view/runs/run1' };
|
|
7
|
+
|
|
8
|
+
const vitestReport = (testResults: VitestJsonReport['testResults'], success = false): VitestJsonReport => ({
|
|
9
|
+
success,
|
|
10
|
+
testResults,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('toTestReport', () => {
|
|
14
|
+
it('reads each test with its status, first failure lines and run', () => {
|
|
15
|
+
const report = toTestReport(
|
|
16
|
+
vitestReport([
|
|
17
|
+
{
|
|
18
|
+
name: '/repo/test/platform/a.test.ts',
|
|
19
|
+
status: 'failed',
|
|
20
|
+
message: '',
|
|
21
|
+
assertionResults: [
|
|
22
|
+
{
|
|
23
|
+
fullName: 'owner/a: works',
|
|
24
|
+
status: 'passed',
|
|
25
|
+
duration: 12.6,
|
|
26
|
+
failureMessages: [],
|
|
27
|
+
meta: run,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
fullName: 'owner/a: fails',
|
|
31
|
+
status: 'failed',
|
|
32
|
+
duration: 3,
|
|
33
|
+
// Every failure counts, also the same one twice (e.g. a retry)
|
|
34
|
+
failureMessages: ['AssertionError: boom\n at a.test.ts:1:1', 'AssertionError: boom\n'],
|
|
35
|
+
meta: {},
|
|
36
|
+
},
|
|
37
|
+
{ fullName: 'owner/a: other', status: 'pending', failureMessages: null },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
]),
|
|
41
|
+
{ cwd, exitCode: 1 },
|
|
42
|
+
);
|
|
43
|
+
expect(report).toStrictEqual({
|
|
44
|
+
kind: 'test-report',
|
|
45
|
+
version: 1,
|
|
46
|
+
success: false,
|
|
47
|
+
summary: { total: 3, passed: 1, failed: 1, skipped: 1 },
|
|
48
|
+
tests: [
|
|
49
|
+
{
|
|
50
|
+
name: 'owner/a: works',
|
|
51
|
+
file: 'test/platform/a.test.ts',
|
|
52
|
+
status: 'passed',
|
|
53
|
+
durationMs: 13,
|
|
54
|
+
failureMessages: [],
|
|
55
|
+
run,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'owner/a: fails',
|
|
59
|
+
file: 'test/platform/a.test.ts',
|
|
60
|
+
status: 'failed',
|
|
61
|
+
durationMs: 3,
|
|
62
|
+
failureMessages: ['AssertionError: boom', 'AssertionError: boom'],
|
|
63
|
+
run: null,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'owner/a: other',
|
|
67
|
+
file: 'test/platform/a.test.ts',
|
|
68
|
+
status: 'skipped',
|
|
69
|
+
durationMs: null,
|
|
70
|
+
failureMessages: [],
|
|
71
|
+
run: null,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
errors: [],
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('reports a test file that failed to load, which has no test to blame', () => {
|
|
79
|
+
const report = toTestReport(
|
|
80
|
+
vitestReport([
|
|
81
|
+
{
|
|
82
|
+
name: '/repo/test/platform/b.test.ts',
|
|
83
|
+
status: 'failed',
|
|
84
|
+
message: "Cannot find package 'foo'\nmore",
|
|
85
|
+
assertionResults: [],
|
|
86
|
+
},
|
|
87
|
+
]),
|
|
88
|
+
{ cwd, exitCode: 1 },
|
|
89
|
+
);
|
|
90
|
+
expect(report.success).toBe(false);
|
|
91
|
+
expect(report.errors).toStrictEqual(["test/platform/b.test.ts: Cannot find package 'foo'"]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('fails when vitest does, even if every test passed', () => {
|
|
95
|
+
const report = toTestReport(vitestReport([], true), { cwd, exitCode: 1 });
|
|
96
|
+
expect(report.success).toBe(false);
|
|
97
|
+
expect(report.errors).toStrictEqual(['Vitest exited with code 1, see its output above']);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('passes when every test passed or was skipped', () => {
|
|
101
|
+
const report = toTestReport(
|
|
102
|
+
vitestReport(
|
|
103
|
+
[
|
|
104
|
+
{
|
|
105
|
+
name: '/repo/test/platform/a.test.ts',
|
|
106
|
+
status: 'passed',
|
|
107
|
+
message: '',
|
|
108
|
+
assertionResults: [{ fullName: 'skipped', status: 'skipped', failureMessages: [] }],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
true,
|
|
112
|
+
),
|
|
113
|
+
{ cwd, exitCode: 0 },
|
|
114
|
+
);
|
|
115
|
+
expect(report.success).toBe(true);
|
|
116
|
+
expect(report.summary).toStrictEqual({ total: 1, passed: 0, failed: 0, skipped: 1 });
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -4,7 +4,12 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CONFIG_FILE_NAME,
|
|
9
|
+
loadActorConfig,
|
|
10
|
+
readBaseConfigFile,
|
|
11
|
+
readConfigFile,
|
|
12
|
+
} from '../../../../../bin/utils/config/load-config.js';
|
|
8
13
|
import type { ResolvedActorConfig } from '../../../../../bin/utils/config/structures/base.js';
|
|
9
14
|
import { ExistingDir, ExistingFile, RelativeDir, RelativeFile } from '../../../../../bin/utils/path/repo-relative.js';
|
|
10
15
|
|
|
@@ -265,3 +270,23 @@ describe('readConfigFile', () => {
|
|
|
265
270
|
await expect(readConfigFile(emptyActorSelection)).rejects.toThrow(/at actors/);
|
|
266
271
|
});
|
|
267
272
|
});
|
|
273
|
+
|
|
274
|
+
describe('readBaseConfigFile', () => {
|
|
275
|
+
it('returns non-file settings without checking Actor paths or reading actor.json', async () => {
|
|
276
|
+
await writeFiles({
|
|
277
|
+
[CONFIG_FILE_NAME]: validConfig([
|
|
278
|
+
{
|
|
279
|
+
folder: 'missing-actor-dir',
|
|
280
|
+
actorFullName: 'myteam/shopify',
|
|
281
|
+
tokenEnvVar: 'APIFY_TOKEN',
|
|
282
|
+
overrideActorContext: ['../outside'],
|
|
283
|
+
},
|
|
284
|
+
]),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await expect(readBaseConfigFile()).resolves.toEqual([
|
|
288
|
+
{ actorFullName: 'myteam/shopify', tokenEnvVar: 'APIFY_TOKEN' },
|
|
289
|
+
]);
|
|
290
|
+
await expect(readConfigFile(emptyActorSelection)).rejects.toThrow();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -4,13 +4,23 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
_privates,
|
|
9
|
+
parseConfigFile,
|
|
10
|
+
parseRepoSettings,
|
|
11
|
+
parseResolvedConfigFile,
|
|
12
|
+
} from '../../../../../bin/utils/config/parser.js';
|
|
13
|
+
import { CONFIG_FILE_STRATEGY, type ResolvedActorConfig } from '../../../../../bin/utils/config/structures/base.js';
|
|
9
14
|
import { GROUPED_PARSER } from '../../../../../bin/utils/config/structures/grouped.js';
|
|
10
15
|
import { LEGACY_PARSER } from '../../../../../bin/utils/config/structures/legacy.js';
|
|
11
16
|
import { ExistingDir, RelativeDir } from '../../../../../bin/utils/path/repo-relative.js';
|
|
12
17
|
|
|
13
|
-
const { selectStrategy, verifyConfiguration } = _privates;
|
|
18
|
+
const { selectStrategy, initializePaths, verifyConfiguration: verifyInitializedConfiguration } = _privates;
|
|
19
|
+
const verifyConfiguration = (body: ResolvedActorConfig[]) => {
|
|
20
|
+
const initialized = initializePaths(body);
|
|
21
|
+
verifyInitializedConfiguration(initialized);
|
|
22
|
+
return initialized;
|
|
23
|
+
};
|
|
14
24
|
|
|
15
25
|
let repoDir: string;
|
|
16
26
|
const originalCwd = process.cwd();
|
|
@@ -95,13 +105,13 @@ describe('verifyConfiguration', () => {
|
|
|
95
105
|
).toThrow(/Duplicate folder/);
|
|
96
106
|
});
|
|
97
107
|
|
|
98
|
-
it('
|
|
108
|
+
it('reports the normalized path for a duplicate folder', () => {
|
|
99
109
|
expect(() =>
|
|
100
110
|
verifyConfiguration([
|
|
101
111
|
actor({ folder: '.', actorFullName: 'myteam/actor-a' }),
|
|
102
112
|
actor({ folder: './', actorFullName: 'myteam/actor-b' }),
|
|
103
113
|
]),
|
|
104
|
-
).toThrow('Duplicate folder "
|
|
114
|
+
).toThrow('Duplicate folder "."');
|
|
105
115
|
});
|
|
106
116
|
|
|
107
117
|
it('rejects two entries pointing at the same actor', () => {
|
|
@@ -150,6 +160,19 @@ describe('verifyConfiguration', () => {
|
|
|
150
160
|
});
|
|
151
161
|
|
|
152
162
|
describe('parseConfigFile', () => {
|
|
163
|
+
it('shares strategy resolution with parsing that does not initialize paths', () => {
|
|
164
|
+
const body = { actors: [actor({ folder: 'missing-actor-dir', overrideActorContext: ['../outside'] })] };
|
|
165
|
+
expect(parseResolvedConfigFile(body)).toEqual([
|
|
166
|
+
{
|
|
167
|
+
actorFullName: 'myteam/shopify',
|
|
168
|
+
folder: 'missing-actor-dir',
|
|
169
|
+
tokenEnvVar: 'APIFY_TOKEN',
|
|
170
|
+
overrideActorContext: ['../outside'],
|
|
171
|
+
},
|
|
172
|
+
]);
|
|
173
|
+
expect(() => parseConfigFile(body)).toThrow();
|
|
174
|
+
});
|
|
175
|
+
|
|
153
176
|
it('validates and normalizes paths after resolving the config', () => {
|
|
154
177
|
const [parsed] = parseConfigFile({ actors: [actor({ folder: './bin/', overrideActorContext: ['test/'] })] });
|
|
155
178
|
expect(parsed.folder).toBeInstanceOf(ExistingDir);
|
|
@@ -210,3 +233,21 @@ describe('parseConfigFile', () => {
|
|
|
210
233
|
).toThrow('Duplicate folder "test"');
|
|
211
234
|
});
|
|
212
235
|
});
|
|
236
|
+
|
|
237
|
+
describe('parseRepoSettings', () => {
|
|
238
|
+
it('defaults platformTestsGlob to test/platform/**', () => {
|
|
239
|
+
expect(parseRepoSettings({ actors: [actor()] })).toStrictEqual({ platformTestsGlob: 'test/platform/**' });
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('reads platformTestsGlob next to the Actor entries, in any mode', () => {
|
|
243
|
+
expect(parseRepoSettings({ platformTestsGlob: 'e2e/**', actors: [actor()] })).toStrictEqual({
|
|
244
|
+
platformTestsGlob: 'e2e/**',
|
|
245
|
+
});
|
|
246
|
+
expect(parseConfigFile({ platformTestsGlob: 'e2e/**', actors: [actor({ folder: 'bin' })] })).toHaveLength(1);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('rejects an empty or non-string platformTestsGlob', () => {
|
|
250
|
+
expect(() => parseRepoSettings({ platformTestsGlob: '' })).toThrow(/platformTestsGlob/);
|
|
251
|
+
expect(() => parseRepoSettings({ platformTestsGlob: ['test/**'] })).toThrow(/platformTestsGlob/);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -68,6 +68,18 @@ describe('GROUPED_PARSER', () => {
|
|
|
68
68
|
expect(() => GROUPED_PARSER.parse({})).toThrow();
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it('rejects an empty group token name', () => {
|
|
72
|
+
expect(() => GROUPED_PARSER.parse({ groups: { myteam: group({ tokenEnvVar: '' }) } })).toThrow(
|
|
73
|
+
/at groups\.myteam\.tokenEnvVar/,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('rejects an empty actor token override instead of falling back to the group token', () => {
|
|
78
|
+
expect(() =>
|
|
79
|
+
GROUPED_PARSER.parse({ groups: { myteam: group({ actors: [actor({ tokenEnvVar: '' })] }) } }),
|
|
80
|
+
).toThrow(/at groups\.myteam\.actors\[0\]\.tokenEnvVar/);
|
|
81
|
+
});
|
|
82
|
+
|
|
71
83
|
it('reports problems from every group at once instead of only the first', () => {
|
|
72
84
|
const message = (() => {
|
|
73
85
|
try {
|
|
@@ -32,6 +32,7 @@ describe('LEGACY_PARSER', () => {
|
|
|
32
32
|
['folder is missing', { folder: undefined }, /at actors\[0\]\.folder/],
|
|
33
33
|
['folder is not a string', { folder: 123 }, /at actors\[0\]\.folder/],
|
|
34
34
|
['tokenEnvVar is missing', { tokenEnvVar: undefined }, /at actors\[0\]\.tokenEnvVar/],
|
|
35
|
+
['tokenEnvVar is empty', { tokenEnvVar: '' }, /at actors\[0\]\.tokenEnvVar/],
|
|
35
36
|
['overrideActorContext is not an array', { overrideActorContext: 'packages' }, /expected array/],
|
|
36
37
|
['overrideActorContext holds non-strings', { overrideActorContext: [123] }, /overrideActorContext\[0\]/],
|
|
37
38
|
])('rejects an entry where %s', (_name, fields, expected) => {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
|
|
7
|
+
const SCRIPT = fileURLToPath(new URL('../../../.github/scripts/mask-config-secrets.sh', import.meta.url));
|
|
8
|
+
|
|
9
|
+
const run = (configSecrets?: string) =>
|
|
10
|
+
spawnSync('bash', [SCRIPT], {
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
env: { PATH: process.env.PATH, ...(configSecrets === undefined ? {} : { CONFIG_SECRETS: configSecrets }) },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const masks = (stdout: string) => stdout.split('\n').filter((line) => line !== '');
|
|
16
|
+
|
|
17
|
+
describe('mask-config-secrets.sh', () => {
|
|
18
|
+
it('registers every value as a mask', () => {
|
|
19
|
+
const { status, stdout, stderr } = run(JSON.stringify({ TOKEN_ONE: 'one', TOKEN_TWO: 'two' }));
|
|
20
|
+
expect(status, stderr).toBe(0);
|
|
21
|
+
expect(masks(stdout)).toStrictEqual(['::add-mask::one', '::add-mask::two']);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('registers a multi-line value line by line, skipping empty lines', () => {
|
|
25
|
+
const { status, stdout, stderr } = run(
|
|
26
|
+
JSON.stringify({ DEPLOY_KEY: '-----BEGIN KEY-----\nabc\n\n-----END KEY-----\n' }),
|
|
27
|
+
);
|
|
28
|
+
expect(status, stderr).toBe(0);
|
|
29
|
+
expect(masks(stdout)).toStrictEqual([
|
|
30
|
+
'::add-mask::-----BEGIN KEY-----',
|
|
31
|
+
'::add-mask::abc',
|
|
32
|
+
'::add-mask::-----END KEY-----',
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('skips values that are not strings', () => {
|
|
37
|
+
const { status, stdout, stderr } = run(JSON.stringify({ TOKEN_ONE: 'one', COUNT: 1 }));
|
|
38
|
+
expect(status, stderr).toBe(0);
|
|
39
|
+
expect(masks(stdout)).toStrictEqual(['::add-mask::one']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it.each([
|
|
43
|
+
['is unset', undefined],
|
|
44
|
+
['is empty', ''],
|
|
45
|
+
])('does nothing when CONFIG_SECRETS %s', (_, configSecrets) => {
|
|
46
|
+
const { status, stdout, stderr } = run(configSecrets);
|
|
47
|
+
expect(status, stderr).toBe(0);
|
|
48
|
+
expect(stdout).toBe('');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it.each([
|
|
52
|
+
['is not JSON', 'apify_api_xyzzy'],
|
|
53
|
+
['is a JSON string', '"apify_api_xyzzy"'],
|
|
54
|
+
['is a JSON array', '["apify_api_xyzzy"]'],
|
|
55
|
+
])('fails without printing the value when the secret %s', (_, configSecrets) => {
|
|
56
|
+
const { status, stdout, stderr } = run(configSecrets);
|
|
57
|
+
expect(status).toBe(1);
|
|
58
|
+
expect(stdout).toContain('::error::The CONFIG_SECRETS secret must be a JSON object.');
|
|
59
|
+
expect(stdout + stderr).not.toContain('xyzzy');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
9
|
+
|
|
10
|
+
const SCRIPT = fileURLToPath(new URL('../../../.github/scripts/run-with-apify-tokens.mjs', import.meta.url));
|
|
11
|
+
|
|
12
|
+
// Three Actors, each with its own token, so a test can leave any of them out.
|
|
13
|
+
const CONFIG = {
|
|
14
|
+
actors: [
|
|
15
|
+
{ folder: 'actors/one', actorFullName: 'team/one', tokenEnvVar: 'TOKEN_ONE' },
|
|
16
|
+
{ folder: 'actors/two', actorFullName: 'team/two', tokenEnvVar: 'TOKEN_TWO' },
|
|
17
|
+
{ folder: 'actors/three', actorFullName: 'team/three', tokenEnvVar: 'TOKEN_THREE' },
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// The command the wrapper runs prints what it was handed, so a test sees exactly what got through.
|
|
22
|
+
const PRINT_ENV = `console.log(JSON.stringify({
|
|
23
|
+
TOKEN_ONE: process.env.TOKEN_ONE,
|
|
24
|
+
TOKEN_TWO: process.env.TOKEN_TWO,
|
|
25
|
+
TOKEN_THREE: process.env.TOKEN_THREE,
|
|
26
|
+
NPM_TOKEN: process.env.NPM_TOKEN,
|
|
27
|
+
WORKFLOW_SECRETS: process.env.WORKFLOW_SECRETS,
|
|
28
|
+
}))`;
|
|
29
|
+
|
|
30
|
+
let cwd: string;
|
|
31
|
+
|
|
32
|
+
// `workflowSecrets` is what the step's `toJSON(secrets)` holds inside the reusable workflow.
|
|
33
|
+
const run = (workflowSecrets: Record<string, string>) => {
|
|
34
|
+
const result = spawnSync(process.execPath, [SCRIPT, process.execPath, '-e', PRINT_ENV], {
|
|
35
|
+
cwd,
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
env: { PATH: process.env.PATH, WORKFLOW_SECRETS: JSON.stringify(workflowSecrets) },
|
|
38
|
+
});
|
|
39
|
+
return { ...result, env: result.status === 0 ? JSON.parse(result.stdout) : undefined };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe('run-with-apify-tokens.mjs', () => {
|
|
43
|
+
beforeAll(async () => {
|
|
44
|
+
cwd = await mkdtemp(path.join(os.tmpdir(), 'run-with-apify-tokens-'));
|
|
45
|
+
await writeFile(path.join(cwd, 'apify-test-tools.config.json'), JSON.stringify(CONFIG));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
await rm(cwd, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('passes on inherited secrets, as `secrets: inherit` sends them', () => {
|
|
53
|
+
const { status, stderr, env } = run({
|
|
54
|
+
TOKEN_ONE: 'one',
|
|
55
|
+
TOKEN_TWO: 'two',
|
|
56
|
+
TOKEN_THREE: 'three',
|
|
57
|
+
NPM_TOKEN: 'npm',
|
|
58
|
+
});
|
|
59
|
+
expect(status, stderr).toBe(0);
|
|
60
|
+
expect(env).toStrictEqual({ TOKEN_ONE: 'one', TOKEN_TWO: 'two', TOKEN_THREE: 'three' });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('unpacks the tokens from the CONFIG_SECRETS secret', () => {
|
|
64
|
+
const { status, stderr, env } = run({
|
|
65
|
+
CONFIG_SECRETS: JSON.stringify({ TOKEN_ONE: 'one', TOKEN_TWO: 'two', TOKEN_THREE: 'three' }),
|
|
66
|
+
NPM_TOKEN: 'npm',
|
|
67
|
+
});
|
|
68
|
+
expect(status, stderr).toBe(0);
|
|
69
|
+
expect(env).toStrictEqual({ TOKEN_ONE: 'one', TOKEN_TWO: 'two', TOKEN_THREE: 'three' });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('passes on only the config names when CONFIG_SECRETS holds every secret of the caller', () => {
|
|
73
|
+
const { status, stderr, env } = run({
|
|
74
|
+
CONFIG_SECRETS: JSON.stringify({
|
|
75
|
+
TOKEN_ONE: 'one',
|
|
76
|
+
TOKEN_TWO: 'two',
|
|
77
|
+
TOKEN_THREE: 'three',
|
|
78
|
+
NPM_TOKEN: 'npm',
|
|
79
|
+
github_token: 'gh',
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
expect(status, stderr).toBe(0);
|
|
83
|
+
expect(env).toStrictEqual({ TOKEN_ONE: 'one', TOKEN_TWO: 'two', TOKEN_THREE: 'three' });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('prefers CONFIG_SECRETS over an inherited secret of the same name', () => {
|
|
87
|
+
const { status, stderr, env } = run({
|
|
88
|
+
CONFIG_SECRETS: JSON.stringify({ TOKEN_ONE: 'from-config-secrets' }),
|
|
89
|
+
TOKEN_ONE: 'inherited',
|
|
90
|
+
TOKEN_TWO: 'inherited',
|
|
91
|
+
});
|
|
92
|
+
expect(status, stderr).toBe(0);
|
|
93
|
+
expect(env).toStrictEqual({ TOKEN_ONE: 'from-config-secrets', TOKEN_TWO: 'inherited' });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('warns about a token the config declares but nothing passes, and runs anyway', () => {
|
|
97
|
+
const { status, stderr, env } = run({
|
|
98
|
+
CONFIG_SECRETS: JSON.stringify({ TOKEN_ONE: 'one', TOKEN_TWO: 'two' }),
|
|
99
|
+
});
|
|
100
|
+
expect(status, stderr).toBe(0);
|
|
101
|
+
expect(stderr).toContain('declares TOKEN_THREE, but no such secret was passed');
|
|
102
|
+
expect(env).toStrictEqual({ TOKEN_ONE: 'one', TOKEN_TWO: 'two' });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it.each([
|
|
106
|
+
['is not JSON', 'apify_api_xyzzy', 'The CONFIG_SECRETS secret is not valid JSON.'],
|
|
107
|
+
['is a JSON string', '"apify_api_xyzzy"', 'The CONFIG_SECRETS secret must be a JSON object.'],
|
|
108
|
+
['is a JSON array', '["apify_api_xyzzy"]', 'The CONFIG_SECRETS secret must be a JSON object.'],
|
|
109
|
+
])('fails without printing the value when CONFIG_SECRETS %s', (_, packed, message) => {
|
|
110
|
+
const { status, stdout, stderr } = run({ CONFIG_SECRETS: packed });
|
|
111
|
+
expect(status).toBe(1);
|
|
112
|
+
expect(stderr).toContain(message);
|
|
113
|
+
expect(stdout + stderr).not.toContain('xyzzy');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { checkRun } from '../../lib/run-checks.js';
|
|
4
|
+
import type { RunTestResult } from '../../lib/run-test-result.js';
|
|
5
|
+
|
|
6
|
+
const fakeRun = (overrides: { status?: string; log?: string; stats?: object; chargedEventCounts?: object } = {}) =>
|
|
7
|
+
({
|
|
8
|
+
status: overrides.status ?? 'SUCCEEDED',
|
|
9
|
+
getRunInfo: async () => ({
|
|
10
|
+
stats: { durationMillis: 5_000 },
|
|
11
|
+
chargedEventCounts: overrides.chargedEventCounts,
|
|
12
|
+
}),
|
|
13
|
+
getDataset: async () => ({ items: new Array(10) }),
|
|
14
|
+
getStatistics: async () => ({
|
|
15
|
+
requestsFailed: 0,
|
|
16
|
+
requestsRetries: 1,
|
|
17
|
+
requestRetryHistogram: [5, 1],
|
|
18
|
+
...overrides.stats,
|
|
19
|
+
}),
|
|
20
|
+
getLog: async () => overrides.log ?? 'all good',
|
|
21
|
+
}) as unknown as RunTestResult;
|
|
22
|
+
|
|
23
|
+
const failures = async (run: RunTestResult, options: Parameters<typeof checkRun>[1]) =>
|
|
24
|
+
(await checkRun(run, options)).filter(({ pass }) => !pass).map(({ name }) => name);
|
|
25
|
+
|
|
26
|
+
describe('checkRun()', () => {
|
|
27
|
+
it('passes a run matching the defaults', async () => {
|
|
28
|
+
expect(await failures(fakeRun(), { datasetItemCount: 10 })).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('reports every failing check at once', async () => {
|
|
32
|
+
const run = fakeRun({ status: 'FAILED', log: 'TypeError: x', stats: { requestsFailed: 2 } });
|
|
33
|
+
expect(await failures(run, { datasetItemCount: { min: 11 } })).toEqual([
|
|
34
|
+
'status',
|
|
35
|
+
'datasetItemCount',
|
|
36
|
+
'failedRequests',
|
|
37
|
+
'forbiddenLogs',
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('disables a check with null', async () => {
|
|
42
|
+
const run = fakeRun({ stats: { requestsFailed: 2 } });
|
|
43
|
+
expect(await failures(run, { datasetItemCount: 10, failedRequests: null })).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('treats explicitly undefined options as disabled checks', async () => {
|
|
47
|
+
const options = { datasetItemCount: 10, duration: undefined, maxRetriesPerRequest: undefined };
|
|
48
|
+
expect(await failures(fakeRun(), options)).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('checks the longest retry chain only when asked to', async () => {
|
|
52
|
+
expect(await failures(fakeRun(), { datasetItemCount: 10, maxRetriesPerRequest: 0 })).toEqual([
|
|
53
|
+
'maxRetriesPerRequest',
|
|
54
|
+
]);
|
|
55
|
+
expect(await failures(fakeRun(), { datasetItemCount: 10, maxRetriesPerRequest: 1 })).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('chargedEventCounts', () => {
|
|
59
|
+
const run = fakeRun({ chargedEventCounts: { start: 1, item: 7 } });
|
|
60
|
+
|
|
61
|
+
it('passes matching counts and intervals', async () => {
|
|
62
|
+
const options = { datasetItemCount: 10, chargedEventCounts: { start: 1, item: { min: 5 } } };
|
|
63
|
+
expect(await failures(run, options)).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('expects unlisted charged events to not happen', async () => {
|
|
67
|
+
const options = { datasetItemCount: 10, chargedEventCounts: { start: 1 } as Record<string, number> };
|
|
68
|
+
expect(await failures(run, options)).toEqual(['chargedEventCounts.item']);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('fails an expected event that was never charged', async () => {
|
|
72
|
+
const options = { datasetItemCount: 10, chargedEventCounts: { start: 1, item: 7, extra: { min: 0 } } };
|
|
73
|
+
expect(await failures(run, options)).toEqual(['chargedEventCounts.extra']);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|