From e07b9890da4523aae31b548f0d005096e0765fb0 Mon Sep 17 00:00:00 2001 From: botandbang <337765928+botandbang@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:35:19 -0400 Subject: [PATCH 1/2] test(ranges): run tests against core's snapshot data at a fixed date the ranges tests fetched the live index.json and schedule.json and used today's clock, so they broke whenever the real schedule left a range empty. on 2026-10-20 no line is in Active LTS, which made lts/latest disappear and failed 5 tests. add test/fixtures.js, which serves core's checked-in snapshots filtered to 2025-09-01 and throws on any other URL. pass it to every ranges call, reuse it in alpha.test.js, add a test that pins each range's newest/oldest/security/lts values on that date, scope the test scripts to *.test.js, and widen the tests-ranges workflow path filter to ranges/** and core's snapshot data. --- .github/workflows/tests-ranges.yml | 3 +- ranges/package.json | 4 +- ranges/test/alpha.test.js | 15 ++------ ranges/test/default.test.js | 5 ++- ranges/test/fixtures.js | 31 +++++++++++++++ ranges/test/known.test.js | 60 +++++++++++++++++++++++++++++- ranges/test/matrixAlias.test.js | 34 ++++++++++------- ranges/test/singleAlias.test.js | 13 ++++--- ranges/test/unknownFilter.test.js | 13 ++++--- ranges/test/v.test.js | 5 ++- 10 files changed, 137 insertions(+), 46 deletions(-) create mode 100644 ranges/test/fixtures.js diff --git a/.github/workflows/tests-ranges.yml b/.github/workflows/tests-ranges.yml index e20d33d7..45a21284 100644 --- a/.github/workflows/tests-ranges.yml +++ b/.github/workflows/tests-ranges.yml @@ -3,7 +3,8 @@ name: "Test Suite: ranges" on: pull_request: paths: - - 'ranges/*' + - 'ranges/**' + - 'core/test/data/static/**' branches: - main workflow_dispatch: diff --git a/ranges/package.json b/ranges/package.json index abaf3456..719edabc 100644 --- a/ranges/package.json +++ b/ranges/package.json @@ -7,8 +7,8 @@ "scripts": { "lint": "biome check ./", "lint:write": "biome check ./ --write", - "test": "node --test", - "coverage": "c8 node --test", + "test": "node --test test/*.test.js", + "coverage": "c8 node --test test/*.test.js", "updates:check": "npx npm-check-updates", "updates:update": "npx npm-check-updates -u" }, diff --git a/ranges/test/alpha.test.js b/ranges/test/alpha.test.js index e2a14a10..633cdaee 100644 --- a/ranges/test/alpha.test.js +++ b/ranges/test/alpha.test.js @@ -1,5 +1,6 @@ const { deepStrictEqual } = require('node:assert'); const ranges = require('../index'); +const fixtures = require('./fixtures'); const { describe, it } = require('node:test'); // these tests use made-up data that follows the release schedule starting with @@ -41,18 +42,8 @@ const schedule = { }, }; -// only serves the releases that would be out by `now`, like the real index.json -function at(now) { - return { - now, - fetch: async (url) => - Response.json( - url.endsWith('schedule.json') - ? schedule - : index.filter((entry) => entry.date <= now), - ), - }; -} +// serves the made-up data above instead of core's snapshots +const at = (now) => fixtures.at(now, { index, schedule }); describe('check to make sure that Alpha releases work as expected', async () => { it('should leave Alpha releases out of "all"', async () => { diff --git a/ranges/test/default.test.js b/ranges/test/default.test.js index 1e43e300..cdef3f49 100644 --- a/ranges/test/default.test.js +++ b/ranges/test/default.test.js @@ -1,10 +1,11 @@ const assert = require('node:assert'); const ranges = require('../index'); +const { at } = require('./fixtures'); const { describe, it } = require('node:test'); describe('data should exist as it is defined', async () => { it('versions should have correct types on every property', async () => { - const data = await ranges(); + const data = await ranges(undefined, at()); for (const key in data) { assert.deepStrictEqual(Array.isArray(data[key].versions), true); assert.deepStrictEqual(data[key].versions.length > 0, true); @@ -16,7 +17,7 @@ describe('data should exist as it is defined', async () => { }); it('both newest and oldest on every property should be the correct type', async () => { - const data = await ranges(); + const data = await ranges(undefined, at()); for (const key in data) { assert.deepStrictEqual(typeof data[key].newest, 'string'); assert.deepStrictEqual(typeof data[key].oldest, 'string'); diff --git a/ranges/test/fixtures.js b/ranges/test/fixtures.js new file mode 100644 index 00000000..b6be339c --- /dev/null +++ b/ranges/test/fixtures.js @@ -0,0 +1,31 @@ +const index = require('../../core/test/data/static/index.json'); +const schedule = require('../../core/test/data/static/schedule.json'); + +// the default date for tests. on 2025-09-01, v24 is Current, v22 is Active +// LTS, v20 is Maintenance LTS, and v23 has reached End of Life, so every +// range has versions. every one of those lines also has a security release +// by then, so no security field is empty either. +const now = '2025-09-01'; + +// returns options for `ranges` that serve `data` (core's index.json and +// schedule.json snapshots by default) instead of fetching them, only including +// releases that would be out by `now`, like the real index.json. any other URL +// throws, so tests never touch the network. +function at(date = now, data = { index, schedule }) { + return { + now: date, + fetch: async (url) => { + if (url.endsWith('/schedule.json')) { + return Response.json(data.schedule); + } + + if (url.endsWith('/index.json')) { + return Response.json(data.index.filter((entry) => entry.date <= date)); + } + + throw new Error(`No fixture data for ${url}`); + }, + }; +} + +module.exports = { at }; diff --git a/ranges/test/known.test.js b/ranges/test/known.test.js index 3b6d3808..eecd8b3d 100644 --- a/ranges/test/known.test.js +++ b/ranges/test/known.test.js @@ -1,18 +1,74 @@ const assert = require('node:assert'); const ranges = require('../index'); +const { at } = require('./fixtures'); const { describe, it } = require('node:test'); describe('check some values we know should exist', async () => { it('should include some known, static values in "all"', async () => { - const data = await ranges(); + const data = await ranges(undefined, at()); assert.deepStrictEqual(data.all.versions.includes('v19.0.0'), true); // a semver major we know exists assert.deepStrictEqual(data.all.versions.includes('v14.21.0'), true); // an LTS release we know happened assert.deepStrictEqual(data.all.versions.includes('v0.1.14'), true); // oldest included data in the array - this is the correct oldest data since it's technically the first entry on nodejs.org/dist/index.json }); it('should include some known, static values in "eol"', async () => { - const data = await ranges(); + const data = await ranges(undefined, at()); assert.deepStrictEqual(data.all.versions.includes('v14.21.0'), true); // an LTS release we know happened assert.deepStrictEqual(data.eol.versions.includes('v0.8.0'), true); // oldest included data in the array - this should be fixed since it's clearly not the oldest in the dataset. }); + + // on 2025-09-01, v24 is Current, v22 is Active LTS, v20 is Maintenance LTS, + // and v23 is the newest End of Life line + it('should put each release line in the right range', async () => { + const data = await ranges(undefined, at()); + const summary = Object.fromEntries( + Object.entries(data).map(([alias, { versions, ...range }]) => [ + alias, + range, + ]), + ); + + assert.deepStrictEqual(summary, { + all: { + newestLts: 'v22.19.0', + newestSecurity: 'v24.4.1', + newest: 'v24.7.0', + oldest: 'v0.1.14', + }, + current: { + oldestSecurity: '24.0.2', + newestSecurity: '24.4.1', + newest: 'v24.7.0', + oldest: 'v24.0.0', + }, + 'lts/latest': { + oldestLts: '22.11.0', + oldestSecurity: '22.4.1', + newestLts: '22.19.0', + newestSecurity: '22.17.1', + newest: 'v22.19.0', + oldest: 'v22.11.0', + }, + 'lts/active': { + oldestLts: '20.9.0', + oldestSecurity: '20.3.1', + newestLts: '22.19.0', + newestSecurity: '22.17.1', + newest: 'v22.19.0', + oldest: 'v20.9.0', + }, + 'lts/maintenance': { + oldestLts: '20.9.0', + oldestSecurity: '20.3.1', + newestLts: '20.19.4', + newestSecurity: '20.19.4', + newest: 'v20.19.4', + oldest: 'v20.9.0', + }, + eol: { + newest: 'v23.11.1', + oldest: 'v0.8.0', + }, + }); + }); }); diff --git a/ranges/test/matrixAlias.test.js b/ranges/test/matrixAlias.test.js index 91c9f9f1..920e311b 100644 --- a/ranges/test/matrixAlias.test.js +++ b/ranges/test/matrixAlias.test.js @@ -1,5 +1,6 @@ const assert = require('node:assert'); const ranges = require('../index'); +const { at } = require('./fixtures'); const { describe, it } = require('node:test'); // this is a non-exhaustive list of sets of aliases. @@ -7,7 +8,7 @@ const { describe, it } = require('node:test'); describe('data should exist as it is defined when calling multiple aliases', async () => { // begin by testing some of the more risky logic in the data construction it('versions should have correct types on every property if an array when "lts/latest" and "lts/maintenance" are passed', async () => { - const data = await ranges(['lts/latest', 'lts/maintenance']); + const data = await ranges(['lts/latest', 'lts/maintenance'], at()); // check lts/latest values assert.deepStrictEqual(Array.isArray(data['lts/latest'].versions), true); @@ -37,7 +38,7 @@ describe('data should exist as it is defined when calling multiple aliases', asy }); it('versions should have correct types on every property if an array when "lts/latest" and "lts/active" are passed', async () => { - const data = await ranges(['lts/latest', 'lts/active']); + const data = await ranges(['lts/latest', 'lts/active'], at()); // check lts/latest values assert.deepStrictEqual(Array.isArray(data['lts/latest'].versions), true); @@ -64,7 +65,7 @@ describe('data should exist as it is defined when calling multiple aliases', asy }); it('versions should have correct types on every property if an array when "lts/maintenance" and "lts/active" are passed', async () => { - const data = await ranges(['lts/maintenance', 'lts/active']); + const data = await ranges(['lts/maintenance', 'lts/active'], at()); // check lts/maintenance values assert.deepStrictEqual( @@ -94,7 +95,10 @@ describe('data should exist as it is defined when calling multiple aliases', asy }); it('versions should have correct types on every property if an array when "lts/maintenance", "lts/active", and "lts/latest" are passed', async () => { - const data = await ranges(['lts/maintenance', 'lts/active', 'lts/latest']); + const data = await ranges( + ['lts/maintenance', 'lts/active', 'lts/latest'], + at(), + ); // check lts/maintenance values assert.deepStrictEqual( @@ -138,7 +142,7 @@ describe('data should exist as it is defined when calling multiple aliases', asy // we want to make sure that we're specifically testing the translations // as a set to ensure that it is direct and clear when they're breaking. it('should pass the "lts" translation ("lts/active" and "lts/maintenance")', async () => { - const data = await ranges(['lts/active', 'lts/maintenance']); + const data = await ranges(['lts/active', 'lts/maintenance'], at()); // check lts/active values assert.deepStrictEqual(Array.isArray(data['lts/active'].versions), true); @@ -171,7 +175,10 @@ describe('data should exist as it is defined when calling multiple aliases', asy }); it('should pass the "supported" translation ("current", "lts/active", and "lts/maintenance")', async () => { - const data = await ranges(['current', 'lts/active', 'lts/maintenance']); + const data = await ranges( + ['current', 'lts/active', 'lts/maintenance'], + at(), + ); // check current values assert.deepStrictEqual(Array.isArray(data.current.versions), true); @@ -211,7 +218,10 @@ describe('data should exist as it is defined when calling multiple aliases', asy }); it('should pass the "supported" translation ("current", "lts/active", and "lts/maintenance")', async () => { - const data = await ranges(['current', 'lts/active', 'lts/maintenance']); + const data = await ranges( + ['current', 'lts/active', 'lts/maintenance'], + at(), + ); // check current values assert.deepStrictEqual(Array.isArray(data.current.versions), true); @@ -251,12 +261,10 @@ describe('data should exist as it is defined when calling multiple aliases', asy }); it('should pass the "all" translation ("current", "lts/active", "lts/maintenance", and "eol")', async () => { - const data = await ranges([ - 'current', - 'lts/active', - 'lts/maintenance', - 'eol', - ]); + const data = await ranges( + ['current', 'lts/active', 'lts/maintenance', 'eol'], + at(), + ); // check current values assert.deepStrictEqual(Array.isArray(data.current.versions), true); diff --git a/ranges/test/singleAlias.test.js b/ranges/test/singleAlias.test.js index 47996ccb..980310bd 100644 --- a/ranges/test/singleAlias.test.js +++ b/ranges/test/singleAlias.test.js @@ -1,10 +1,11 @@ const assert = require('node:assert'); const ranges = require('../index'); +const { at } = require('./fixtures'); const { describe, it } = require('node:test'); describe('data should exist as it is defined when calling a single alias', async () => { it('versions should have correct types on every property if an array with "all" is passed', async () => { - const data = await ranges(['all']); + const data = await ranges(['all'], at()); assert.deepStrictEqual(Array.isArray(data.all.versions), true); assert.deepStrictEqual(data.all.versions.length > 0, true); assert.deepStrictEqual( @@ -14,7 +15,7 @@ describe('data should exist as it is defined when calling a single alias', async }); it('versions should have correct types on every property if an array with "current" is passed', async () => { - const data = await ranges(['current']); + const data = await ranges(['current'], at()); assert.deepStrictEqual(Array.isArray(data.current.versions), true); assert.deepStrictEqual(data.current.versions.length > 0, true); assert.deepStrictEqual( @@ -24,7 +25,7 @@ describe('data should exist as it is defined when calling a single alias', async }); it('versions should have correct types on every property if an array with "lts/latest" is passed', async () => { - const data = await ranges(['lts/latest']); + const data = await ranges(['lts/latest'], at()); assert.deepStrictEqual(Array.isArray(data['lts/latest'].versions), true); assert.deepStrictEqual(data['lts/latest'].versions.length > 0, true); assert.deepStrictEqual( @@ -40,7 +41,7 @@ describe('data should exist as it is defined when calling a single alias', async }); it('versions should have correct types on every property if an array with "lts/active" is passed', async () => { - const data = await ranges(['lts/active']); + const data = await ranges(['lts/active'], at()); assert.deepStrictEqual(Array.isArray(data['lts/active'].versions), true); assert.deepStrictEqual(data['lts/active'].versions.length > 0, true); assert.deepStrictEqual( @@ -56,7 +57,7 @@ describe('data should exist as it is defined when calling a single alias', async }); it('versions should have correct types on every property if an array with "lts/maintenance" is passed', async () => { - const data = await ranges(['lts/maintenance']); + const data = await ranges(['lts/maintenance'], at()); assert.deepStrictEqual( Array.isArray(data['lts/maintenance'].versions), true, @@ -75,7 +76,7 @@ describe('data should exist as it is defined when calling a single alias', async }); it('versions should have correct types on every property if an array with "eol" is passed', async () => { - const data = await ranges(['eol']); + const data = await ranges(['eol'], at()); assert.deepStrictEqual(Array.isArray(data.eol.versions), true); assert.deepStrictEqual(data.eol.versions.length > 0, true); assert.deepStrictEqual( diff --git a/ranges/test/unknownFilter.test.js b/ranges/test/unknownFilter.test.js index cd47ab8b..bd4b73b8 100644 --- a/ranges/test/unknownFilter.test.js +++ b/ranges/test/unknownFilter.test.js @@ -1,11 +1,12 @@ const assert = require('node:assert'); const ranges = require('../index'); +const { at } = require('./fixtures'); const { describe, it } = require('node:test'); describe('failure states for arguments', async () => { it('should throw an error on a string with an unknown value', async () => { try { - const data = await ranges('poopin'); + const data = await ranges('poopin', at()); if (data) throw new Error('Should not have gotten here'); } catch (error) { assert.deepStrictEqual( @@ -17,7 +18,7 @@ describe('failure states for arguments', async () => { it('should throw an error on a string with an unknown value', async () => { try { - const data = await ranges('poopin'); + const data = await ranges('poopin', at()); if (data) throw new Error('Should not have gotten here'); } catch (error) { assert.deepStrictEqual( @@ -29,7 +30,7 @@ describe('failure states for arguments', async () => { it('should throw an error on a single unknown array entry', async () => { try { - const data = await ranges(['poopin']); + const data = await ranges(['poopin'], at()); if (data) throw new Error('Should not have gotten here'); } catch (error) { assert.deepStrictEqual( @@ -41,7 +42,7 @@ describe('failure states for arguments', async () => { it('should throw an error on an unknown array entry where multiple entries exist and it is the first entry', async () => { try { - const data = await ranges(['poopin', 'lts/active']); + const data = await ranges(['poopin', 'lts/active'], at()); if (data) throw new Error('Should not have gotten here'); } catch (error) { assert.deepStrictEqual( @@ -53,7 +54,7 @@ describe('failure states for arguments', async () => { it('should throw an error on an unknown array entry where multiple entries exist and it is the first entry', async () => { try { - const data = await ranges(['lts/active', 'poopin']); + const data = await ranges(['lts/active', 'poopin'], at()); if (data) throw new Error('Should not have gotten here'); } catch (error) { assert.deepStrictEqual( @@ -65,7 +66,7 @@ describe('failure states for arguments', async () => { it('should throw an error on passing an Object', async () => { try { - const data = await ranges({}); + const data = await ranges({}, at()); if (data) throw new Error('Should not have gotten here'); } catch (error) { assert.deepStrictEqual( diff --git a/ranges/test/v.test.js b/ranges/test/v.test.js index 38bea7b3..534926af 100644 --- a/ranges/test/v.test.js +++ b/ranges/test/v.test.js @@ -1,11 +1,12 @@ const assert = require('node:assert'); const ranges = require('../index'); +const { at } = require('./fixtures'); const { describe, it } = require('node:test'); // tests various values to ensure that the start with the correct character describe('check that "v"s are where they should (and are not where they should not be)', async () => { it('should include the letter v before the strings that should have it', async () => { - const data = await ranges(); + const data = await ranges(undefined, at()); // loop over all versions arrays in all sets (all, // current, lts/latest, lts/active, lts/maintenance, @@ -29,7 +30,7 @@ describe('check that "v"s are where they should (and are not where they should n // this probably needs to be fixed. It's a bug in core and it trickles down to ranges. it('is missing the letter v where it should not be... for now', async () => { - const data = await ranges(); + const data = await ranges(undefined, at()); assert.deepStrictEqual(data.current.oldestSecurity.startsWith('v'), false); assert.deepStrictEqual(data.current.newestSecurity.startsWith('v'), false); From bf7378728f10b62df71fd6eb72dd26c97b4ff2c1 Mon Sep 17 00:00:00 2001 From: botandbang <337765928+botandbang@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:05:38 -0400 Subject: [PATCH 2/2] ci(ranges): run the ranges tests when core or aliases change the ranges tests now run the workspace core and aliases, not just core's snapshot data, so changes to either should trigger them. --- .github/workflows/tests-ranges.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests-ranges.yml b/.github/workflows/tests-ranges.yml index 45a21284..7c958684 100644 --- a/.github/workflows/tests-ranges.yml +++ b/.github/workflows/tests-ranges.yml @@ -4,7 +4,8 @@ on: pull_request: paths: - 'ranges/**' - - 'core/test/data/static/**' + - 'core/**' + - 'aliases/**' branches: - main workflow_dispatch: