Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions core/test/properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const assert = require('node:assert');
const { describe, it } = require('node:test');
const fc = require('fast-check');
const semver = require('semver');
const nodevu = require('../index');
const { at } = require('../util/dev/fixtures');

const phases = ['alpha', 'start', 'lts', 'maintenance', 'end'];

describe('core', async () => {
it('should key every release by its raw version, under its own line', async () => {
await fc.assert(
fc.asyncProperty(day(), async (now) => {
// Arrange
const options = at(now);

// Act
const data = await nodevu(options);

// Assert
for (const [line, { releases }] of Object.entries(data)) {
for (const [key, release] of Object.entries(releases)) {
assert.strictEqual(release.semver.line, line);
assert.strictEqual(key, `v${release.semver.raw}`);
}
}
}),
);
});

it('should list every security release newest first, bounded by newest and oldest', async () => {
await fc.assert(
fc.asyncProperty(day(), async (now) => {
// Arrange
const options = at(now);

// Act
const data = await nodevu(options);

// Assert
for (const { releases, security } of Object.values(data)) {
const flagged = Object.values(releases)
.filter((release) => release.security.isSecurity)
.map((release) => release.semver.raw);

assert.deepStrictEqual(
security.all,
[...security.all].sort(semver.rcompare),
);
assert.strictEqual(security.newest, security.all[0]);
assert.strictEqual(security.oldest, security.all.at(-1));
assert.deepStrictEqual([...security.all].sort(), flagged.sort());
}
}),
);
});

it('should bound support.lts by two LTS releases, newest >= oldest', async () => {
await fc.assert(
fc.asyncProperty(day(), async (now) => {
// Arrange
const options = at(now);

// Act
const data = await nodevu(options);

// Assert
for (const { releases, support } of Object.values(data)) {
const newest = support?.lts?.newest;
const oldest = support?.lts?.oldest;

if (newest === undefined || oldest === undefined) {
continue;
}

assert.ok(semver.gte(newest, oldest), `${newest} < ${oldest}`);
assert.strictEqual(releases[`v${newest}`].lts.isLts, true);
assert.strictEqual(releases[`v${oldest}`].lts.isLts, true);
}
}),
);
});

it('should only ever move a line forward through its phases', async () => {
await fc.assert(
fc.asyncProperty(day(), day(), async (a, b) => {
// Arrange
const [earlier, later] = [a, b].sort();

// Act
const before = await nodevu(at(earlier));
const after = await nodevu(at(later));

// Assert
for (const [line, { support }] of Object.entries(before)) {
assert.ok(
phaseIndex(after[line].support) >= phaseIndex(support),
`${line} went from ${support?.phases.current} to ${after[line].support?.phases.current}`,
);
}
}),
);
});
});

// Helpers

// a day anywhere in the fixture's span, as YYYY-MM-DD
function day() {
return fc
.date({
min: new Date('2009-06-01'),
max: new Date('2026-10-01'),
noInvalidDate: true,
})
.map((date) => date.toISOString().slice(0, 10));
}

// a phase that hasn't begun (`{}`) sorts before every phase
function phaseIndex(support) {
return phases.indexOf(String(support?.phases.current));
}
1 change: 1 addition & 0 deletions opt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"homepage": "https://github.com/cutenode/nodevu#readme",
"devDependencies": {
"c8": "^12.0.0",
"fast-check": "^4.10.2",
"luxon": "^3.5.0",
"undici": "^8.12.2"
},
Expand Down
50 changes: 49 additions & 1 deletion opt/test/test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { deepStrictEqual } = require('node:assert');
const { deepStrictEqual, ok, strictEqual } = require('node:assert');
const { describe, it } = require('node:test');
const fc = require('fast-check');
const { fetch: undiciFetch } = require('undici');
const { DateTime } = require('luxon');
const opt = require('../index');
Expand Down Expand Up @@ -73,3 +74,50 @@ describe('the parseOptions module should still work when defaults are changed',
deepStrictEqual(defaultParsedOptions.urls.schedule, 'https://example.com');
});
});

describe('opt', async () => {
it('should use each option passed and the default for each one left out', async () => {
await fc.assert(
fc.property(options(), (input) => {
// Arrange
const defaults = opt();

// Act
const result = opt(input);

// Assert
strictEqual(result.fetch, input.fetch ?? defaults.fetch);
strictEqual(
result.urls.index,
input.urls?.index ?? defaults.urls.index,
);
strictEqual(
result.urls.schedule,
input.urls?.schedule ?? defaults.urls.schedule,
);

if (input.now === undefined) {
ok(DateTime.isDateTime(result.now));
} else {
strictEqual(result.now, input.now);
}
}),
);
});
});

// Helpers

// any subset of the options opt takes, each one set to a usable value
function options() {
const value = fc.string({ minLength: 1 });

return fc.record(
{
fetch: fc.func(fc.anything()),
now: value,
urls: fc.record({ index: value, schedule: value }, { requiredKeys: [] }),
},
{ requiredKeys: [] },
);
}
3 changes: 2 additions & 1 deletion parsefiles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"author": "Tierney Cyren <hello@bnb.im>",
"license": "MIT",
"devDependencies": {
"c8": "^12.0.0"
"c8": "^12.0.0",
"fast-check": "^4.10.2"
},
"repository": {
"type": "git",
Expand Down
96 changes: 96 additions & 0 deletions parsefiles/test/parseFilename.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const assert = require('node:assert');
const { describe, it } = require('node:test');
const fc = require('fast-check');

const parseFilename = require('../index');
const releases = require('../../core/test/data/static/index.json');

describe('parseFilename', () => {
it('should only list the win-x86 directory for win-x86-exe', () => {
Expand Down Expand Up @@ -46,4 +48,98 @@ describe('parseFilename', () => {
// Assert
assert.deepStrictEqual(result.files, ['node-v4.0.0-darwin-x86.tar.gz']);
});

it('should always set a type for a real id', async () => {
await fc.assert(
fc.property(realIdArbitrary(), versionArbitrary(), (id, version) => {
// Arrange (by the arbitraries)

// Act
const result = parseFilename(id, version);

// Assert
assert.notStrictEqual(result.type, undefined);
}),
);
});

it('should always keep the id it was given', async () => {
await fc.assert(
fc.property(realIdArbitrary(), versionArbitrary(), (id, version) => {
// Arrange (by the arbitraries)

// Act
const result = parseFilename(id, version);

// Assert
assert.strictEqual(result.id, id);
}),
);
});

it('should never return the bare id as a file name', async () => {
await fc.assert(
fc.property(realIdArbitrary(), versionArbitrary(), (id, version) => {
// Arrange (by the arbitraries)

// Act
const result = parseFilename(id, version);

// Assert
assert.ok(!result.files.includes(id), result.files.join(', '));
}),
);
});

it('should always name files after the version or a win-* directory', async () => {
await fc.assert(
fc.property(realIdArbitrary(), versionArbitrary(), (id, version) => {
// Arrange
const isNamed = (/** @type {string} */ name) =>
name.includes(`v${version}`) || /^win-[^/]+\/[^/]+$/.test(name);

// Act
const result = parseFilename(id, version);

// Assert
assert.ok(result.files.every(isNamed), result.files.join(', '));
}),
);
});

it('should always set an architecture for a real id other than src and headers', async () => {
await fc.assert(
fc.property(
realIdArbitrary(['src', 'headers']),
versionArbitrary(),
(id, version) => {
// Arrange (by the arbitraries)

// Act
const result = parseFilename(id, version);

// Assert
assert.notStrictEqual(result.architecture, undefined);
},
),
);
});
});

// Helpers

/**
* Every id in the `files` of a release in the release index snapshot.
*
* @param {string[]} [excluded] Ids to leave out.
*/
function realIdArbitrary(excluded = []) {
const ids = new Set(releases.flatMap((release) => release.files));

return fc.constantFrom(...[...ids].filter((id) => !excluded.includes(id)));
}

/** A version without its `v`, such as `'17.7.0'`. */
function versionArbitrary() {
return fc.tuple(fc.nat(), fc.nat(), fc.nat()).map((parts) => parts.join('.'));
}
3 changes: 2 additions & 1 deletion ranges/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"@nodevu/core": "^1.0.0"
},
"devDependencies": {
"c8": "^12.0.0"
"c8": "^12.0.0",
"fast-check": "^4.10.2"
},
"types": "types/index.d.ts"
}
Loading
Loading