apify-test-tools 0.10.2-beta.1 → 0.10.2-beta.11
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 +2 -0
- package/.github/actionlint.yaml +11 -0
- package/.github/actions/checkout-restore-dependencies/action.yaml +64 -28
- package/.github/cli/apify-test-tools.mjs +360 -0
- package/.github/scripts/mask-config-secrets.sh +17 -0
- package/.github/scripts/run-with-apify-tokens.mjs +37 -12
- package/.github/workflows/_check_code.yaml +25 -16
- package/.github/workflows/_move_major_tag.yaml +5 -83
- package/.github/workflows/_tests.yaml +34 -0
- package/.github/workflows/_update_release_metadata.yaml +0 -12
- package/.github/workflows/manual_move_major_tag.yaml +3 -3
- package/.github/workflows/manual_release_stable.yaml +3 -13
- package/.github/workflows/on_master.yaml +9 -5
- package/.github/workflows/on_pull_request.yaml +3 -48
- package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +16 -3
- package/.github/workflows/public_platform-tests.yaml +27 -3
- package/.github/workflows/public_pr-build-test.yaml +67 -29
- package/.github/workflows/public_push-build-latest.yaml +44 -10
- package/.github/workflows/public_review.yaml +5 -4
- package/.husky/pre-commit +5 -0
- package/.prettierignore +3 -0
- package/CHANGELOG.md +17 -0
- package/CONTRIBUTING.md +112 -24
- package/README.md +218 -600
- package/bin/affected-actors.ts +198 -0
- package/bin/build-from-local.ts +65 -13
- package/bin/build.ts +123 -41
- package/bin/diff-changes.ts +32 -23
- package/bin/diff-json-schema.ts +5 -6
- package/bin/git.ts +69 -99
- package/bin/main.ts +96 -135
- package/bin/slack.ts +43 -10
- package/bin/stdin-input.ts +65 -0
- package/bin/test-report.ts +6 -9
- package/bin/types.ts +15 -0
- package/dist/bin/affected-actors.d.ts +46 -0
- package/dist/bin/affected-actors.d.ts.map +1 -0
- package/dist/bin/affected-actors.js +135 -0
- package/dist/bin/affected-actors.js.map +1 -0
- package/dist/bin/build-from-local.d.ts +12 -3
- package/dist/bin/build-from-local.d.ts.map +1 -1
- package/dist/bin/build-from-local.js +34 -12
- package/dist/bin/build-from-local.js.map +1 -1
- package/dist/bin/build.d.ts +30 -7
- package/dist/bin/build.d.ts.map +1 -1
- package/dist/bin/build.js +85 -26
- package/dist/bin/build.js.map +1 -1
- package/dist/bin/diff-changes.d.ts +11 -4
- package/dist/bin/diff-changes.d.ts.map +1 -1
- package/dist/bin/diff-changes.js +19 -19
- 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 +23 -21
- package/dist/bin/git.d.ts.map +1 -1
- package/dist/bin/git.js +60 -83
- package/dist/bin/git.js.map +1 -1
- package/dist/bin/main.js +82 -105
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/slack.d.ts +14 -4
- package/dist/bin/slack.d.ts.map +1 -1
- package/dist/bin/slack.js +39 -6
- package/dist/bin/slack.js.map +1 -1
- package/dist/bin/stdin-input.d.ts +17 -0
- package/dist/bin/stdin-input.d.ts.map +1 -0
- package/dist/bin/stdin-input.js +54 -0
- package/dist/bin/stdin-input.js.map +1 -0
- package/dist/bin/test-report.d.ts.map +1 -1
- package/dist/bin/test-report.js +6 -8
- package/dist/bin/test-report.js.map +1 -1
- package/dist/bin/types.d.ts +13 -0
- package/dist/bin/types.d.ts.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 -2
- package/dist/lib/lib.d.ts.map +1 -1
- package/dist/lib/lib.js +15 -22
- 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.d.ts +2 -0
- package/dist/test/bundle/cli-bundle.test.d.ts.map +1 -0
- package/dist/test/bundle/cli-bundle.test.js +190 -0
- package/dist/test/bundle/cli-bundle.test.js.map +1 -0
- package/dist/test/e2e/cli-bundle.e2e.test.d.ts +2 -0
- package/dist/test/e2e/cli-bundle.e2e.test.d.ts.map +1 -0
- package/dist/test/e2e/cli-bundle.e2e.test.js +138 -0
- package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -0
- 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/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 +167 -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 +141 -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 +44 -0
- package/dist/test/unit/bin/build-cli.test.js.map +1 -0
- package/dist/test/unit/bin/build-from-local.test.js +65 -1
- package/dist/test/unit/bin/build-from-local.test.js.map +1 -1
- package/dist/test/unit/bin/build.test.js +223 -3
- 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 +16 -0
- package/dist/test/unit/bin/compiled-cli.js.map +1 -0
- package/dist/test/unit/bin/diff-changes.test.js +130 -101
- 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 +69 -152
- package/dist/test/unit/bin/git.test.js.map +1 -1
- package/dist/test/unit/bin/slack.test.d.ts +2 -0
- package/dist/test/unit/bin/slack.test.d.ts.map +1 -0
- package/dist/test/unit/bin/slack.test.js +71 -0
- package/dist/test/unit/bin/slack.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 +50 -0
- package/dist/test/unit/bin/stdin-input.test.js.map +1 -0
- 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/docs/cli.md +373 -0
- package/docs/lib.md +271 -0
- package/docs/workflows.md +370 -0
- package/lib/extend-expect.ts +48 -258
- package/lib/lib.ts +24 -28
- package/lib/run-checks.ts +101 -0
- package/lib/run-test-result.ts +45 -55
- package/package.json +13 -3
- package/test/bundle/cli-bundle.test.ts +235 -0
- package/test/e2e/cli-bundle.e2e.test.ts +207 -0
- package/test/e2e/git-build-release.e2e.test.ts +113 -0
- package/test/e2e/helpers.ts +82 -0
- package/test/unit/bin/affected-actors-cli.test.ts +201 -0
- package/test/unit/bin/affected-actors.test.ts +182 -0
- package/test/unit/bin/build-cli.test.ts +55 -0
- package/test/unit/bin/build-from-local.test.ts +71 -0
- package/test/unit/bin/build.test.ts +279 -3
- package/test/unit/bin/compiled-cli.ts +17 -0
- package/test/unit/bin/diff-changes.test.ts +139 -101
- package/test/unit/bin/diff.test.ts +25 -28
- package/test/unit/bin/git.test.ts +85 -176
- package/test/unit/bin/slack.test.ts +84 -0
- package/test/unit/bin/stdin-input.test.ts +61 -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/.github/scripts/check-major-tag-refs.mjs +0 -72
- package/.github/scripts/check-package-version-bump.mjs +0 -74
- package/.github/workflows-package-version +0 -1
|
@@ -4,10 +4,7 @@ import { beforeEach, describe, expect, it, test, vi } from 'vitest';
|
|
|
4
4
|
import { isCosmeticOnlyJsonSchemaChange } from '../../../bin/diff-json-schema.js';
|
|
5
5
|
import * as Utils from '../../../bin/utils.js';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
{ sha: 'Commit1', author: '', date: '', message: '' },
|
|
9
|
-
{ sha: 'Commit3', author: '', date: '', message: '' },
|
|
10
|
-
];
|
|
7
|
+
const range = { fromRef: 'Commit0', toRef: 'Commit3' };
|
|
11
8
|
|
|
12
9
|
describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
13
10
|
let gitCommandSpy: MockInstance;
|
|
@@ -23,7 +20,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
23
20
|
test('returns true when nothing changed', async () => {
|
|
24
21
|
const json = JSON.stringify({ type: 'string', title: 'My field' });
|
|
25
22
|
mockGitCalls(json, json);
|
|
26
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
23
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
27
24
|
});
|
|
28
25
|
|
|
29
26
|
test('returns true when only title changes', async () => {
|
|
@@ -31,7 +28,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
31
28
|
JSON.stringify({ type: 'string', title: 'Old title' }),
|
|
32
29
|
JSON.stringify({ type: 'string', title: 'New title' }),
|
|
33
30
|
);
|
|
34
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
31
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
35
32
|
});
|
|
36
33
|
|
|
37
34
|
test('returns true when only description changes', async () => {
|
|
@@ -39,7 +36,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
39
36
|
JSON.stringify({ type: 'string', description: 'Old desc' }),
|
|
40
37
|
JSON.stringify({ type: 'string', description: 'New desc' }),
|
|
41
38
|
);
|
|
42
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
39
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
43
40
|
});
|
|
44
41
|
|
|
45
42
|
test('returns true when only example changes', async () => {
|
|
@@ -47,7 +44,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
47
44
|
JSON.stringify({ type: 'string', example: 'foo' }),
|
|
48
45
|
JSON.stringify({ type: 'string', example: 'bar' }),
|
|
49
46
|
);
|
|
50
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
47
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
51
48
|
});
|
|
52
49
|
|
|
53
50
|
test('returns true when only enumTitles changes', async () => {
|
|
@@ -55,7 +52,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
55
52
|
JSON.stringify({ type: 'string', enum: ['a', 'b'], enumTitles: ['Option A', 'Option B'] }),
|
|
56
53
|
JSON.stringify({ type: 'string', enum: ['a', 'b'], enumTitles: ['Choice A', 'Choice B'] }),
|
|
57
54
|
);
|
|
58
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
55
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
59
56
|
});
|
|
60
57
|
|
|
61
58
|
test('returns true when only sectionCaption changes', async () => {
|
|
@@ -63,12 +60,12 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
63
60
|
JSON.stringify({ sectionCaption: 'Old caption' }),
|
|
64
61
|
JSON.stringify({ sectionCaption: 'New caption' }),
|
|
65
62
|
);
|
|
66
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
63
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
67
64
|
});
|
|
68
65
|
|
|
69
66
|
test('returns true when only sectionDescription changes', async () => {
|
|
70
67
|
mockGitCalls(JSON.stringify({ sectionDescription: 'Old' }), JSON.stringify({ sectionDescription: 'New' }));
|
|
71
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
68
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
72
69
|
});
|
|
73
70
|
|
|
74
71
|
test('returns false when a functional field (type) changes', async () => {
|
|
@@ -76,17 +73,17 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
76
73
|
JSON.stringify({ type: 'string', title: 'My field' }),
|
|
77
74
|
JSON.stringify({ type: 'integer', title: 'My field' }),
|
|
78
75
|
);
|
|
79
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
76
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
80
77
|
});
|
|
81
78
|
|
|
82
79
|
test('returns false when a functional field is added', async () => {
|
|
83
80
|
mockGitCalls(JSON.stringify({ title: 'My field' }), JSON.stringify({ title: 'My field', type: 'string' }));
|
|
84
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
81
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
85
82
|
});
|
|
86
83
|
|
|
87
84
|
test('returns false when a functional field is removed', async () => {
|
|
88
85
|
mockGitCalls(JSON.stringify({ type: 'string', title: 'My field' }), JSON.stringify({ title: 'My field' }));
|
|
89
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
86
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
90
87
|
});
|
|
91
88
|
|
|
92
89
|
test('returns true when non-functional fields change at nested level (input schema properties)', async () => {
|
|
@@ -104,7 +101,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
104
101
|
},
|
|
105
102
|
}),
|
|
106
103
|
);
|
|
107
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
104
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
108
105
|
});
|
|
109
106
|
|
|
110
107
|
test('returns false when a functional nested field changes alongside non-functional ones', async () => {
|
|
@@ -118,7 +115,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
118
115
|
properties: { myField: { type: 'integer', title: 'New title' } },
|
|
119
116
|
}),
|
|
120
117
|
);
|
|
121
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
118
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
122
119
|
});
|
|
123
120
|
|
|
124
121
|
test('returns false when a new property is added to input schema', async () => {
|
|
@@ -126,7 +123,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
126
123
|
JSON.stringify({ properties: { fieldA: { type: 'string' } } }),
|
|
127
124
|
JSON.stringify({ properties: { fieldA: { type: 'string' }, fieldB: { type: 'integer' } } }),
|
|
128
125
|
);
|
|
129
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
126
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
130
127
|
});
|
|
131
128
|
|
|
132
129
|
test('returns false when a property is removed from input schema', async () => {
|
|
@@ -134,27 +131,27 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
134
131
|
JSON.stringify({ properties: { fieldA: { type: 'string' }, fieldB: { type: 'integer' } } }),
|
|
135
132
|
JSON.stringify({ properties: { fieldA: { type: 'string' } } }),
|
|
136
133
|
);
|
|
137
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
134
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
138
135
|
});
|
|
139
136
|
|
|
140
137
|
test('returns false when invalid JSON in old content', async () => {
|
|
141
138
|
mockGitCalls('not json', '{}');
|
|
142
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
139
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
143
140
|
});
|
|
144
141
|
|
|
145
142
|
test('returns false when invalid JSON in new content', async () => {
|
|
146
143
|
mockGitCalls('{}', 'not json');
|
|
147
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
144
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
148
145
|
});
|
|
149
146
|
|
|
150
147
|
test('returns false when git cannot read a schema version', async () => {
|
|
151
148
|
gitCommandSpy.mockRejectedValueOnce(new Error('missing schema'));
|
|
152
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
149
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
153
150
|
});
|
|
154
151
|
|
|
155
152
|
test('returns false when array content changes (not under a non-functional key)', async () => {
|
|
156
153
|
mockGitCalls(JSON.stringify({ enum: [1, 2, 3] }), JSON.stringify({ enum: [1, 2, 4] }));
|
|
157
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
154
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
158
155
|
});
|
|
159
156
|
|
|
160
157
|
test('returns true when example array changes (non-functional key)', async () => {
|
|
@@ -162,7 +159,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
162
159
|
JSON.stringify({ type: 'array', example: [1, 2, 3] }),
|
|
163
160
|
JSON.stringify({ type: 'array', example: [4, 5, 6] }),
|
|
164
161
|
);
|
|
165
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
162
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
166
163
|
});
|
|
167
164
|
|
|
168
165
|
test('returns true for a realistic actor.json with only title/description change', async () => {
|
|
@@ -182,7 +179,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
182
179
|
version: '1.0',
|
|
183
180
|
}),
|
|
184
181
|
);
|
|
185
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
182
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
186
183
|
});
|
|
187
184
|
|
|
188
185
|
test('returns true when fields under properties are reordered', async () => {
|
|
@@ -196,7 +193,7 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
196
193
|
properties: { fieldC: { type: 'boolean' }, fieldA: { type: 'string' }, fieldB: { type: 'integer' } },
|
|
197
194
|
}),
|
|
198
195
|
);
|
|
199
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
196
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(true);
|
|
200
197
|
});
|
|
201
198
|
|
|
202
199
|
test('returns false for a realistic actor.json with version change', async () => {
|
|
@@ -204,14 +201,14 @@ describe('isCosmeticOnlyJsonSchemaChange', () => {
|
|
|
204
201
|
JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'My Actor', version: '1.0' }),
|
|
205
202
|
JSON.stringify({ actorSpecification: 1, name: 'my-actor', title: 'My Actor', version: '1.1' }),
|
|
206
203
|
);
|
|
207
|
-
await expect(isCosmeticOnlyJsonSchemaChange(
|
|
204
|
+
await expect(isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json')).resolves.toBe(false);
|
|
208
205
|
});
|
|
209
206
|
|
|
210
207
|
it('uses correct git refs: first-commit parent for old, last commit for new', async () => {
|
|
211
208
|
const json = JSON.stringify({});
|
|
212
209
|
mockGitCalls(json, json);
|
|
213
|
-
await isCosmeticOnlyJsonSchemaChange(
|
|
214
|
-
expect(gitCommandSpy).toHaveBeenCalledWith(['show', '
|
|
210
|
+
await isCosmeticOnlyJsonSchemaChange(range, 'actors/foo/actor.json');
|
|
211
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(['show', 'Commit0:actors/foo/actor.json']);
|
|
215
212
|
expect(gitCommandSpy).toHaveBeenCalledWith(['show', 'Commit3:actors/foo/actor.json']);
|
|
216
213
|
});
|
|
217
214
|
});
|
|
@@ -2,15 +2,16 @@ import type { MockInstance } from 'vitest';
|
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
findCommitSha,
|
|
6
6
|
getChangedFiles,
|
|
7
|
+
getChangedFilesBetween,
|
|
7
8
|
getCommits,
|
|
9
|
+
getCommitsBetween,
|
|
8
10
|
getCurrentBranch,
|
|
9
|
-
getReleaseChanges,
|
|
10
11
|
getRepoName,
|
|
11
|
-
hasMergeFromTarget,
|
|
12
12
|
normalizeRepoUrl,
|
|
13
13
|
parseBaseCommit,
|
|
14
|
+
resolveCommitSha,
|
|
14
15
|
resolveReleaseBaseCommit,
|
|
15
16
|
resolveRepoUrl,
|
|
16
17
|
} from '../../../bin/git.js';
|
|
@@ -123,9 +124,12 @@ describe('getChangedFiles', () => {
|
|
|
123
124
|
expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);
|
|
124
125
|
|
|
125
126
|
expect(gitCommandSpy).toHaveBeenCalledTimes(1);
|
|
126
|
-
expect(gitCommandSpy).toHaveBeenCalledWith(
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(
|
|
128
|
+
['diff', '--name-only', '--no-renames', `${firstSha}~..${lastSha}`],
|
|
129
|
+
{
|
|
130
|
+
operation: 'get the list of changed files between commits',
|
|
131
|
+
},
|
|
132
|
+
);
|
|
129
133
|
});
|
|
130
134
|
|
|
131
135
|
it('should throw without running git when the commit list is empty', async () => {
|
|
@@ -146,7 +150,7 @@ describe('getChangedFiles', () => {
|
|
|
146
150
|
expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);
|
|
147
151
|
|
|
148
152
|
expect(gitCommandSpy).toHaveBeenCalledTimes(1);
|
|
149
|
-
expect(gitCommandSpy).toHaveBeenCalledWith(['diff', '--name-only', `${onlySha}~..${onlySha}`], {
|
|
153
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(['diff', '--name-only', '--no-renames', `${onlySha}~..${onlySha}`], {
|
|
150
154
|
operation: 'get the list of changed files between commits',
|
|
151
155
|
});
|
|
152
156
|
});
|
|
@@ -167,124 +171,6 @@ describe('getChangedFiles', () => {
|
|
|
167
171
|
});
|
|
168
172
|
});
|
|
169
173
|
|
|
170
|
-
describe('hasMergeFromTarget', () => {
|
|
171
|
-
const sourceBranch = 'feature-branch';
|
|
172
|
-
const targetBranch = 'main';
|
|
173
|
-
const mergeSha = 'f'.repeat(40);
|
|
174
|
-
const branchParentSha = 'b'.repeat(40);
|
|
175
|
-
const targetParentSha = 't'.repeat(40);
|
|
176
|
-
const differentMergeBase = '0'.repeat(40);
|
|
177
|
-
|
|
178
|
-
let gitCommandSpy: MockInstance;
|
|
179
|
-
|
|
180
|
-
beforeEach(() => {
|
|
181
|
-
gitCommandSpy = vi.spyOn(Utils, 'runGitCommand');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('should return false when there are no merge commits on the branch', async () => {
|
|
185
|
-
gitCommandSpy.mockImplementation(async (args: string[]) => {
|
|
186
|
-
if (args.includes('--merges')) return '';
|
|
187
|
-
return '';
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(false);
|
|
191
|
-
expect(gitCommandSpy).toHaveBeenCalledWith(
|
|
192
|
-
['log', '--merges', '--pretty=format:%H', `${targetBranch}..${sourceBranch}`],
|
|
193
|
-
{ operation: 'find merge commits on the source branch that are absent from the target branch' },
|
|
194
|
-
);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should return true when a merge commit has a parent reachable from targetBranch', async () => {
|
|
198
|
-
gitCommandSpy.mockImplementation(async (args: string[]) => {
|
|
199
|
-
if (args.includes('--merges')) return mergeSha;
|
|
200
|
-
if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${targetParentSha}`;
|
|
201
|
-
if (args[0] === 'merge-base' && args[1] === branchParentSha) return differentMergeBase;
|
|
202
|
-
if (args[0] === 'merge-base' && args[1] === targetParentSha) return targetParentSha;
|
|
203
|
-
return '';
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(true);
|
|
207
|
-
expect(gitCommandSpy).toHaveBeenCalledWith(['merge-base', targetParentSha, targetBranch], {
|
|
208
|
-
operation: 'check whether a merge parent is reachable from the target branch',
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('should return false when the merge commit parent is not reachable from targetBranch (unrelated branch merge)', async () => {
|
|
213
|
-
const unrelatedSha = 'e'.repeat(40);
|
|
214
|
-
gitCommandSpy.mockImplementation(async (args: string[]) => {
|
|
215
|
-
if (args.includes('--merges')) return mergeSha;
|
|
216
|
-
if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${unrelatedSha}`;
|
|
217
|
-
// merge-base returns something other than the parent — not an ancestor
|
|
218
|
-
if (args[0] === 'merge-base') return differentMergeBase;
|
|
219
|
-
return '';
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(false);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('continues after merge-base reports unrelated histories', async () => {
|
|
226
|
-
gitCommandSpy.mockImplementation(async (args: string[]) => {
|
|
227
|
-
if (args.includes('--merges')) return mergeSha;
|
|
228
|
-
if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${targetParentSha}`;
|
|
229
|
-
if (args[0] === 'merge-base' && args[1] === branchParentSha) {
|
|
230
|
-
throw new Utils.GitCommandError(args, Object.assign(new Error('no common ancestor'), { code: 1 }));
|
|
231
|
-
}
|
|
232
|
-
if (args[0] === 'merge-base' && args[1] === targetParentSha) return targetParentSha;
|
|
233
|
-
return '';
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
await expect(hasMergeFromTarget(sourceBranch, targetBranch)).resolves.toBe(true);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('propagates unexpected Git failures', async () => {
|
|
240
|
-
gitCommandSpy.mockImplementation(
|
|
241
|
-
async (args: string[], options?: Parameters<typeof Utils.runGitCommand>[1]) => {
|
|
242
|
-
if (args.includes('--merges')) return mergeSha;
|
|
243
|
-
if (args.includes('--pretty=format:%P')) return `${branchParentSha} ${targetParentSha}`;
|
|
244
|
-
throw new Utils.GitCommandError(
|
|
245
|
-
args,
|
|
246
|
-
Object.assign(new Error('Git failed'), { code: 128 }),
|
|
247
|
-
options?.operation,
|
|
248
|
-
);
|
|
249
|
-
},
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
await expect(hasMergeFromTarget(sourceBranch, targetBranch)).rejects.toThrow(
|
|
253
|
-
'Failed to check whether a merge parent is reachable from the target branch.\n' +
|
|
254
|
-
'Command: git "merge-base"',
|
|
255
|
-
);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe('getBranchOnlyChangedFiles', () => {
|
|
260
|
-
const sourceBranch = 'feature-branch';
|
|
261
|
-
const targetBranch = 'main';
|
|
262
|
-
|
|
263
|
-
let gitCommandSpy: MockInstance;
|
|
264
|
-
|
|
265
|
-
beforeEach(() => {
|
|
266
|
-
gitCommandSpy = vi.spyOn(Utils, 'runGitCommand');
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should return files touched by non-merge commits', async () => {
|
|
270
|
-
gitCommandSpy.mockResolvedValue('README.md\n\nactors/foo_bar/src/main.ts\n');
|
|
271
|
-
|
|
272
|
-
const result = await getBranchOnlyChangedFiles(sourceBranch, targetBranch);
|
|
273
|
-
|
|
274
|
-
expect(result).toStrictEqual(['README.md', 'actors/foo_bar/src/main.ts']);
|
|
275
|
-
expect(gitCommandSpy).toHaveBeenCalledWith(
|
|
276
|
-
['log', '--no-merges', '--name-only', '--pretty=format:', `${targetBranch}..${sourceBranch}`],
|
|
277
|
-
{ operation: 'list files changed by non-merge commits on the source branch' },
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('should return empty array when there are no non-merge commits', async () => {
|
|
282
|
-
gitCommandSpy.mockResolvedValue('');
|
|
283
|
-
|
|
284
|
-
await expect(getBranchOnlyChangedFiles(sourceBranch, targetBranch)).resolves.toStrictEqual([]);
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
|
|
288
174
|
const VALID_SHA = 'a'.repeat(40);
|
|
289
175
|
const VALID_JSON = JSON.stringify({ sha: VALID_SHA, author: 'test', date: 'now', message: 'msg' });
|
|
290
176
|
|
|
@@ -320,7 +206,7 @@ describe('getCurrentBranch', () => {
|
|
|
320
206
|
const gitCommandSpy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue('master');
|
|
321
207
|
await expect(getCurrentBranch()).resolves.toBe('master');
|
|
322
208
|
expect(gitCommandSpy).toHaveBeenCalledWith(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
323
|
-
operation: 'determine the
|
|
209
|
+
operation: 'determine the checked-out branch',
|
|
324
210
|
});
|
|
325
211
|
});
|
|
326
212
|
|
|
@@ -474,71 +360,94 @@ describe('resolveReleaseBaseCommit', () => {
|
|
|
474
360
|
it('should throw on an invalid SHA', async () => {
|
|
475
361
|
await expect(resolveReleaseBaseCommit('not-a-sha')).rejects.toThrow('Invalid base commit SHA');
|
|
476
362
|
});
|
|
363
|
+
|
|
364
|
+
it('checks ancestry against a custom head', async () => {
|
|
365
|
+
const gitCommandSpy = vi.spyOn(Utils, 'runGitCommand').mockImplementation(async (args: string[]) => {
|
|
366
|
+
if (args[0] === 'rev-parse') return baseSha;
|
|
367
|
+
if (args[0] === 'merge-base') return 'c'.repeat(40);
|
|
368
|
+
return '';
|
|
369
|
+
});
|
|
370
|
+
await expect(resolveReleaseBaseCommit(baseSha, 'origin/release')).rejects.toThrow(
|
|
371
|
+
'is not an ancestor of origin/release',
|
|
372
|
+
);
|
|
373
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(['merge-base', baseSha, 'origin/release'], {
|
|
374
|
+
operation: 'check whether the release base commit is an ancestor of origin/release',
|
|
375
|
+
});
|
|
376
|
+
});
|
|
477
377
|
});
|
|
478
378
|
|
|
479
|
-
describe('
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
379
|
+
describe('getCommitsBetween', () => {
|
|
380
|
+
it('lists the commits after fromRef up to toRef, oldest first', async () => {
|
|
381
|
+
const older = `${'1'.repeat(40)}»¦«Dev<dev@example.com>»¦«Date1»¦«first`;
|
|
382
|
+
const newer = `${'2'.repeat(40)}»¦«Dev<dev@example.com>»¦«Date2»¦«second`;
|
|
383
|
+
const spy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue(`${newer}\n${older}`);
|
|
484
384
|
|
|
485
|
-
|
|
385
|
+
const commits = await getCommitsBetween('base-sha', 'head-sha');
|
|
486
386
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (args[0] === 'log') return `${mergeCommit}\n${mergedBranchCommit}`;
|
|
491
|
-
if (args.includes('--name-only')) return 'actors/foo/src/main.ts\nCHANGELOG.md';
|
|
492
|
-
if (args[0] === 'diff')
|
|
493
|
-
return 'diff --git a/CHANGELOG.md b/CHANGELOG.md\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -1 +1,2 @@\n+- Added foo\n # Changelog';
|
|
494
|
-
return '';
|
|
387
|
+
expect(commits.map((c) => c.message)).toStrictEqual(['first', 'second']);
|
|
388
|
+
expect(spy).toHaveBeenCalledWith(['log', '--pretty=format:%H»¦«%aN<%aE>»¦«%aD»¦«%s', 'base-sha..head-sha'], {
|
|
389
|
+
operation: 'list commits reachable from the source ref but absent from the target ref',
|
|
495
390
|
});
|
|
496
391
|
});
|
|
392
|
+
});
|
|
393
|
+
describe('empty commit ranges', () => {
|
|
394
|
+
// Real git on this repository's own history: HEAD..HEAD has no commits
|
|
395
|
+
beforeEach(() => {
|
|
396
|
+
vi.restoreAllMocks();
|
|
397
|
+
});
|
|
497
398
|
|
|
498
|
-
it('
|
|
499
|
-
|
|
399
|
+
it('getCommitsBetween returns no commits', async () => {
|
|
400
|
+
await expect(getCommitsBetween('HEAD', 'HEAD')).resolves.toStrictEqual([]);
|
|
401
|
+
});
|
|
500
402
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
{ sha: headSha, author: 'Dev<dev@example.com>', date: 'Date2', message: 'Merge pull request #1' },
|
|
505
|
-
],
|
|
506
|
-
changedFiles: ['actors/foo/src/main.ts', 'CHANGELOG.md'],
|
|
507
|
-
changelog: '- Added foo',
|
|
508
|
-
});
|
|
509
|
-
// Not from the parent of the oldest commit, which may predate the base commit for merge commits
|
|
510
|
-
expect(gitRunSpy).toHaveBeenCalledWith(['rev-parse', 'HEAD'], {
|
|
511
|
-
operation: 'identify the current release commit',
|
|
512
|
-
});
|
|
513
|
-
expect(gitRunSpy).toHaveBeenCalledWith(['diff', '--name-only', baseSha, 'HEAD'], {
|
|
514
|
-
operation: 'list files changed since the last release',
|
|
515
|
-
});
|
|
516
|
-
expect(gitRunSpy).toHaveBeenCalledWith(
|
|
517
|
-
['log', '--pretty=format:%H»¦«%aN<%aE>»¦«%aD»¦«%s', `${baseSha}..HEAD`],
|
|
518
|
-
{ operation: 'list commits reachable from the source ref but absent from the target ref' },
|
|
403
|
+
it('getCommits fails clearly, since the commands built on it need at least one commit', async () => {
|
|
404
|
+
await expect(getCommits({ sourceBranch: 'HEAD', targetBranch: 'HEAD' })).rejects.toThrow(
|
|
405
|
+
'HEAD has no commits that are not on HEAD.',
|
|
519
406
|
);
|
|
520
|
-
expect(gitRunSpy).toHaveBeenCalledWith(['diff', baseSha, 'HEAD', '--', 'CHANGELOG.md'], {
|
|
521
|
-
operation: 'read changelog changes since the last release',
|
|
522
|
-
});
|
|
523
407
|
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
describe('getChangedFilesBetween', () => {
|
|
411
|
+
it('diffs the two endpoints directly', async () => {
|
|
412
|
+
const spy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue('src/main.ts\nCHANGELOG.md');
|
|
524
413
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
414
|
+
await expect(getChangedFilesBetween('base-sha', 'head-sha')).resolves.toStrictEqual([
|
|
415
|
+
'src/main.ts',
|
|
416
|
+
'CHANGELOG.md',
|
|
417
|
+
]);
|
|
418
|
+
expect(spy).toHaveBeenCalledWith(['diff', '--name-only', '--no-renames', 'base-sha..head-sha'], {
|
|
419
|
+
operation: 'get the list of changed files between commits',
|
|
530
420
|
});
|
|
531
421
|
});
|
|
422
|
+
});
|
|
532
423
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
424
|
+
describe('resolveCommitSha', () => {
|
|
425
|
+
it('resolves a ref to the commit it points to', async () => {
|
|
426
|
+
const sha = 'a'.repeat(40);
|
|
427
|
+
const spy = vi.spyOn(Utils, 'runGitCommand').mockResolvedValue(sha);
|
|
428
|
+
await expect(resolveCommitSha('origin/feature')).resolves.toBe(sha);
|
|
429
|
+
expect(spy).toHaveBeenCalledWith(['rev-parse', '--verify', 'origin/feature^{commit}'], {
|
|
430
|
+
operation: 'resolve origin/feature to a commit',
|
|
539
431
|
});
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe('findCommitSha', () => {
|
|
436
|
+
// Real git on this repository's own history, since what matters is how git answers
|
|
437
|
+
beforeEach(() => {
|
|
438
|
+
vi.restoreAllMocks();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('resolves a ref in the local history to its commit', async () => {
|
|
442
|
+
await expect(findCommitSha('HEAD')).resolves.toMatch(/^[0-9a-f]{40}$/);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('returns undefined for a commit missing from the local history', async () => {
|
|
446
|
+
await expect(findCommitSha('d'.repeat(40))).resolves.toBeUndefined();
|
|
447
|
+
});
|
|
540
448
|
|
|
541
|
-
|
|
542
|
-
|
|
449
|
+
it('rethrows other git failures', async () => {
|
|
450
|
+
vi.spyOn(Utils, 'runGitCommand').mockRejectedValue(new Error('not a git repository'));
|
|
451
|
+
await expect(findCommitSha('HEAD')).rejects.toThrow('not a git repository');
|
|
543
452
|
});
|
|
544
453
|
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { WebClient } from '@slack/web-api';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { notifyToSlack } from '../../../bin/slack.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('@slack/web-api', () => ({ WebClient: vi.fn() }));
|
|
7
|
+
|
|
8
|
+
const TOKEN_ENV_VAR = 'SLACK_TOKEN_RELEASES_BOT';
|
|
9
|
+
const postMessage = vi.fn();
|
|
10
|
+
const authTest = vi.fn();
|
|
11
|
+
|
|
12
|
+
const notify = async (overrides: Partial<Parameters<typeof notifyToSlack>[0]> = {}) =>
|
|
13
|
+
notifyToSlack({
|
|
14
|
+
repoUrl: 'git@github.com:owner/repo',
|
|
15
|
+
changedFiles: ['src/main.ts'],
|
|
16
|
+
changelog: '- Fixed a bug',
|
|
17
|
+
commits: [{ sha: '1', author: 'Test<test@example.com>', date: '', message: 'fix: bug' }],
|
|
18
|
+
dryRun: true,
|
|
19
|
+
releaseSlackChannel: '#releases',
|
|
20
|
+
reportSlackChannel: '#notif',
|
|
21
|
+
...overrides,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('notifyToSlack', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.stubEnv(TOKEN_ENV_VAR, 'token');
|
|
27
|
+
vi.mocked(WebClient).mockImplementation(function () {
|
|
28
|
+
return { chat: { postMessage }, auth: { test: authTest } };
|
|
29
|
+
} as never);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
vi.unstubAllEnvs();
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should verify the token but post nothing in a dry run', async () => {
|
|
38
|
+
await notify();
|
|
39
|
+
expect(authTest).toHaveBeenCalledTimes(1);
|
|
40
|
+
expect(postMessage).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should still require the token in a dry run', async () => {
|
|
44
|
+
vi.stubEnv(TOKEN_ENV_VAR, '');
|
|
45
|
+
await expect(notify()).rejects.toThrow(`${TOKEN_ENV_VAR} not defined`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should fail a dry run when Slack rejects the token', async () => {
|
|
49
|
+
authTest.mockRejectedValueOnce(new Error('An API error occurred: invalid_auth'));
|
|
50
|
+
await expect(notify()).rejects.toThrow(`${TOKEN_ENV_VAR} was rejected by Slack`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should not need a token when no channel is passed', async () => {
|
|
54
|
+
vi.stubEnv(TOKEN_ENV_VAR, '');
|
|
55
|
+
await notify({ dryRun: false, releaseSlackChannel: undefined, reportSlackChannel: undefined });
|
|
56
|
+
expect(WebClient).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should post to both channels outside of a dry run', async () => {
|
|
60
|
+
await notify({ dryRun: false });
|
|
61
|
+
expect(authTest).not.toHaveBeenCalled();
|
|
62
|
+
expect(postMessage).toHaveBeenCalledTimes(2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should announce nothing, and need no token, when there are no new commits', async () => {
|
|
66
|
+
vi.stubEnv(TOKEN_ENV_VAR, '');
|
|
67
|
+
await notify({ commits: [], dryRun: false });
|
|
68
|
+
expect(WebClient).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should announce the release by the author of the newest commit, named after the repository', async () => {
|
|
72
|
+
const commit = (author: string, message: string) => ({ sha: '1', author, date: '', message });
|
|
73
|
+
await notify({
|
|
74
|
+
commits: [commit('Old<old@example.com>', 'fix: older'), commit('New<new@example.com>', 'feat: newer')],
|
|
75
|
+
repoUrl: 'git@github.com:owner/my-repo',
|
|
76
|
+
dryRun: false,
|
|
77
|
+
releaseSlackChannel: undefined,
|
|
78
|
+
});
|
|
79
|
+
expect(postMessage).toHaveBeenCalledWith({
|
|
80
|
+
channel: '#notif',
|
|
81
|
+
text: expect.stringContaining('*my-repo* – New release (by New)'),
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|