1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @format
8 */
9
10const {
11  parseVersion,
12  isReleaseBranch,
13  validateBuildType,
14} = require('../version-utils');
15
16let execResult = null;
17jest.mock('shelljs', () => ({
18  exec: () => {
19    return {
20      stdout: execResult,
21    };
22  },
23  echo: message => {
24    console.log(message);
25  },
26  exit: exitCode => {
27    exit(exitCode);
28  },
29}));
30
31describe('version-utils', () => {
32  describe('isReleaseBranch', () => {
33    it('should identify as release branch', () => {
34      expect(isReleaseBranch('v0.66-stable')).toBe(true);
35      expect(isReleaseBranch('0.66-stable')).toBe(true);
36      expect(isReleaseBranch('made-up-stuff-stable')).toBe(true);
37    });
38    it('should not identify as release branch', () => {
39      expect(isReleaseBranch('main')).toBe(false);
40      expect(isReleaseBranch('pull/32659')).toBe(false);
41    });
42  });
43
44  describe('parseVersion', () => {
45    it('should throw error if buildType is undefined', () => {
46      function testInvalidVersion() {
47        parseVersion('v0.10.5');
48      }
49      expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
50        `"Unsupported build type: undefined"`,
51      );
52    });
53
54    it('should throw error if buildType is not `release`, `dry-run` or `nightly`', () => {
55      function testInvalidVersion() {
56        parseVersion('v0.10.5', 'invalid_build_type');
57      }
58      expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
59        `"Unsupported build type: invalid_build_type"`,
60      );
61    });
62    it('should throw error if invalid match with release', () => {
63      function testInvalidVersion() {
64        parseVersion('<invalid version>', 'release');
65      }
66      expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
67        `"You must pass a correctly formatted version; couldn't parse <invalid version>"`,
68      );
69    });
70    it('should throw error if invalid match with dry-run', () => {
71      function testInvalidVersion() {
72        parseVersion('<invalid version>', 'dry-run');
73      }
74      expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
75        `"You must pass a correctly formatted version; couldn't parse <invalid version>"`,
76      );
77    });
78    it('should throw error if invalid match with nightly', () => {
79      function testInvalidVersion() {
80        parseVersion('<invalid version>', 'nightly');
81      }
82      expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
83        `"You must pass a correctly formatted version; couldn't parse <invalid version>"`,
84      );
85    });
86
87    it('should parse pre-release version with release and `.`', () => {
88      const {version, major, minor, patch, prerelease} = parseVersion(
89        '0.66.0-rc.4',
90        'release',
91      );
92      expect(version).toBe('0.66.0-rc.4');
93      expect(major).toBe('0');
94      expect(minor).toBe('66');
95      expect(patch).toBe('0');
96      expect(prerelease).toBe('rc.4');
97    });
98
99    it('should parse pre-release version with release and `-`', () => {
100      const {version, major, minor, patch, prerelease} = parseVersion(
101        '0.66.0-rc-4',
102        'release',
103      );
104      expect(version).toBe('0.66.0-rc-4');
105      expect(major).toBe('0');
106      expect(minor).toBe('66');
107      expect(patch).toBe('0');
108      expect(prerelease).toBe('rc-4');
109    });
110
111    it('should reject pre-release version with random prerelease pattern', () => {
112      function testInvalidVersion() {
113        parseVersion('0.66.0-something_invalid', 'release');
114      }
115      expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
116        `"Version 0.66.0-something_invalid is not valid for Release"`,
117      );
118    });
119
120    it('should parse stable version', () => {
121      const {version, major, minor, patch, prerelease} = parseVersion(
122        '0.66.0',
123        'release',
124      );
125      expect(version).toBe('0.66.0');
126      expect(major).toBe('0');
127      expect(minor).toBe('66');
128      expect(patch).toBe('0');
129      expect(prerelease).toBeUndefined();
130    });
131
132    it('should parse pre-release version from tag', () => {
133      const {version, major, minor, patch, prerelease} = parseVersion(
134        'v0.66.0-rc.4',
135        'release',
136      );
137      expect(version).toBe('0.66.0-rc.4');
138      expect(major).toBe('0');
139      expect(minor).toBe('66');
140      expect(patch).toBe('0');
141      expect(prerelease).toBe('rc.4');
142    });
143
144    it('should reject pre-release version from tag with random prerelease pattern', () => {
145      function testInvalidVersion() {
146        parseVersion('v0.66.0-something_invalid', 'release');
147      }
148      expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
149        `"Version 0.66.0-something_invalid is not valid for Release"`,
150      );
151    });
152
153    it('should parse stable version from tag', () => {
154      const {version, major, minor, patch, prerelease} = parseVersion(
155        'v0.66.0',
156        'release',
157      );
158      expect(version).toBe('0.66.0');
159      expect(major).toBe('0');
160      expect(minor).toBe('66');
161      expect(patch).toBe('0');
162      expect(prerelease).toBeUndefined();
163    });
164
165    it('should reject nightly with no prerelease', () => {
166      // this should fail
167      function testInvalidFunction() {
168        parseVersion('0.0.0', 'nightly');
169      }
170      expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
171        `"Version 0.0.0 is not valid for nightlies"`,
172      );
173    });
174
175    it('should reject nightly with prerelease but wrong version numbers', () => {
176      // this should fail
177      function testInvalidFunction() {
178        parseVersion('1.2.3-pre-release', 'nightly');
179      }
180      expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
181        `"Version 1.2.3-pre-release is not valid for nightlies"`,
182      );
183    });
184
185    it('should parse nightly with 0.0.0 and a prerelease part', () => {
186      // this should fail
187      const {version, major, minor, patch, prerelease} = parseVersion(
188        '0.0.0-pre-release',
189        'nightly',
190      );
191
192      expect(version).toBe('0.0.0-pre-release');
193      expect(major).toBe('0');
194      expect(minor).toBe('0');
195      expect(patch).toBe('0');
196      expect(prerelease).toBe('pre-release');
197    });
198    it('should parse dryrun with release version', () => {
199      const {version, major, minor, patch, prerelease} = parseVersion(
200        '0.7.3',
201        'dry-run',
202      );
203      expect(version).toBe('0.7.3');
204      expect(major).toBe('0');
205      expect(minor).toBe('7');
206      expect(patch).toBe('3');
207      expect(prerelease).toBeUndefined();
208    });
209
210    it('should parse dryrun with prerelease . version', () => {
211      const {version, major, minor, patch, prerelease} = parseVersion(
212        '0.20.0-rc.0',
213        'dry-run',
214      );
215      expect(version).toBe('0.20.0-rc.0');
216      expect(major).toBe('0');
217      expect(minor).toBe('20');
218      expect(patch).toBe('0');
219      expect(prerelease).toBe('rc.0');
220    });
221
222    it('should parse dryrun with prerelease - version', () => {
223      const {version, major, minor, patch, prerelease} = parseVersion(
224        '0.20.0-rc-0',
225        'dry-run',
226      );
227      expect(version).toBe('0.20.0-rc-0');
228      expect(major).toBe('0');
229      expect(minor).toBe('20');
230      expect(patch).toBe('0');
231      expect(prerelease).toBe('rc-0');
232    });
233
234    it('should parse dryrun with main version', () => {
235      const {version, major, minor, patch, prerelease} = parseVersion(
236        '1000.0.0',
237        'dry-run',
238      );
239      expect(version).toBe('1000.0.0');
240      expect(major).toBe('1000');
241      expect(minor).toBe('0');
242      expect(patch).toBe('0');
243      expect(prerelease).toBeUndefined();
244    });
245
246    it('should fail for dryrun with v1000.0.1 version', () => {
247      function testInvalidFunction() {
248        parseVersion('v1000.0.1', 'dry-run');
249      }
250      expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
251        `"Version 1000.0.1 is not valid for dry-runs"`,
252      );
253    });
254    it('should parse dryrun with nightly version', () => {
255      const {version, major, minor, patch, prerelease} = parseVersion(
256        '0.0.0-something-else',
257        'dry-run',
258      );
259      expect(version).toBe('0.0.0-something-else');
260      expect(major).toBe('0');
261      expect(minor).toBe('0');
262      expect(patch).toBe('0');
263      expect(prerelease).toBe('something-else');
264    });
265
266    it('should reject dryrun invalid values', () => {
267      function testInvalidFunction() {
268        parseVersion('1000.0.4', 'dry-run');
269      }
270      expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
271        `"Version 1000.0.4 is not valid for dry-runs"`,
272      );
273    });
274
275    it('should reject dryrun for invalid prerelease', () => {
276      function testInvalidFunction() {
277        parseVersion('0.6.4-something-else', 'dry-run');
278      }
279      expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
280        `"Version 0.6.4-something-else is not valid for dry-runs"`,
281      );
282    });
283
284    it('should reject dryrun for nightlies with invalid prerelease', () => {
285      function testInvalidFunction() {
286        parseVersion('0.0.0', 'dry-run');
287      }
288      expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
289        `"Version 0.0.0 is not valid for dry-runs"`,
290      );
291    });
292  });
293
294  describe('Validate version', () => {
295    it('Throw error if the buildType is unknown', () => {
296      function testInvalidFunction() {
297        validateBuildType('wrong_build');
298      }
299      expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
300        `"Unsupported build type: wrong_build"`,
301      );
302    });
303    it('Does not throw if the buildType is release', () => {
304      function testValidCall() {
305        validateBuildType('release');
306      }
307      expect(testValidCall).not.toThrowError();
308    });
309    it('Does not throw if the buildType is nightly', () => {
310      function testValidCall() {
311        validateBuildType('nightly');
312      }
313      expect(testValidCall).not.toThrowError();
314    });
315    it('Does not throw if the buildType is dry-run', () => {
316      function testValidCall() {
317        validateBuildType('dry-run');
318      }
319      expect(testValidCall).not.toThrowError();
320    });
321  });
322});
323