mirror of
https://github.com/webinstall/webi-installers.git
synced 2026-06-09 17:26:33 +00:00
New test programs that exercise the cache-only Node server path and compare its output against the legacy upstream-fetching path: - test-cache-api-ready.js: pre-flight check that the cache file matches what /api/releases would return for each name in _cache/<yyyy-mm>/. - test-cache-compat.js: parameterized cache vs. fresh-fetch diff suite, walks every package and OS/arch combo. - test-api-compat.js, test-installer-resolve.js, test-broad-resolve.js: installer-side parity checks across the resolver. - test-live-compare.js, test-live-cache-diff.js, test-live-installer-diff.js: TSV-output sweeps comparing two remote URLs across the cached package x OS/arch matrix; supports --concurrency and --packages filters. - test-fleet-diff.js: fleet-wide TSV diff with package/OS/arch filtering, used to validate beta and next. Also adds 36 golden snapshots under _webi/testdata/live_*.json covering 6 packages (bat, caddy, go, jq, node, rg) x 4 OS/arch combos plus the unfiltered baseline per package.
278 lines
8.5 KiB
JavaScript
278 lines
8.5 KiB
JavaScript
'use strict';
|
|
|
|
// Tests that _cache JSON files are ready for direct use by the API endpoint
|
|
// (transform-releases.js) WITHOUT needing normalize.js to fix up fields.
|
|
//
|
|
// These tests drive GOER to add missing features to ExportLegacy so that
|
|
// normalize.js can be eliminated from the API path entirely.
|
|
//
|
|
// Usage: node _webi/test-cache-api-ready.js
|
|
|
|
var Fs = require('node:fs');
|
|
var Os = require('node:os');
|
|
var Path = require('node:path');
|
|
|
|
var CACHE_DIR = Path.join(Os.homedir(), '.cache/webi/legacy');
|
|
|
|
// Packages to spot-check (mix of github_releases, custom releases.js, gittag)
|
|
var CHECK_PKGS = [
|
|
'bat',
|
|
'caddy',
|
|
'go',
|
|
'hugo',
|
|
'jq',
|
|
'node',
|
|
'rg',
|
|
'terraform',
|
|
'zig',
|
|
];
|
|
|
|
function loadReleases(dir, pkg) {
|
|
var file = Path.join(dir, pkg + '.json');
|
|
if (!Fs.existsSync(file)) {
|
|
return null;
|
|
}
|
|
try {
|
|
return JSON.parse(Fs.readFileSync(file, 'utf8'));
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
var passes = 0;
|
|
var failures = 0;
|
|
|
|
if (!Fs.existsSync(CACHE_DIR)) {
|
|
console.error('No cache directory at ' + CACHE_DIR);
|
|
process.exit(1);
|
|
}
|
|
console.log('Testing ' + CACHE_DIR);
|
|
console.log('');
|
|
|
|
// ================================================================
|
|
// Test 1: Version has no 'v' prefix
|
|
// ================================================================
|
|
console.log('=== Test 1: Version v-prefix stripped ===');
|
|
console.log('');
|
|
|
|
for (var vi = 0; vi < CHECK_PKGS.length; vi++) {
|
|
var vpkg = CHECK_PKGS[vi];
|
|
var vdata = loadReleases(CACHE_DIR, vpkg);
|
|
if (!vdata) {
|
|
console.log(' SKIP ' + vpkg + ': no data');
|
|
continue;
|
|
}
|
|
|
|
var vPrefixed = 0;
|
|
for (var vri = 0; vri < vdata.releases.length; vri++) {
|
|
var ver = vdata.releases[vri].version || '';
|
|
if (ver.startsWith('v')) {
|
|
vPrefixed++;
|
|
}
|
|
}
|
|
|
|
if (vPrefixed === 0) {
|
|
console.log(' PASS ' + vpkg + ': no v-prefixed versions');
|
|
passes++;
|
|
} else {
|
|
console.log(' FAIL ' + vpkg + ': ' + vPrefixed + '/' + vdata.releases.length + ' versions have v prefix');
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Test 2: libc is never empty string (should be "none", "gnu", "musl", "msvc")
|
|
// ================================================================
|
|
console.log('');
|
|
console.log('=== Test 2: libc never empty ===');
|
|
console.log('');
|
|
|
|
for (var li = 0; li < CHECK_PKGS.length; li++) {
|
|
var lpkg = CHECK_PKGS[li];
|
|
var ldata = loadReleases(CACHE_DIR, lpkg);
|
|
if (!ldata) {
|
|
console.log(' SKIP ' + lpkg + ': no data');
|
|
continue;
|
|
}
|
|
|
|
var emptyLibc = 0;
|
|
for (var lri = 0; lri < ldata.releases.length; lri++) {
|
|
if (ldata.releases[lri].libc === '') {
|
|
emptyLibc++;
|
|
}
|
|
}
|
|
|
|
if (emptyLibc === 0) {
|
|
console.log(' PASS ' + lpkg + ': all entries have libc set');
|
|
passes++;
|
|
} else {
|
|
console.log(' FAIL ' + lpkg + ': ' + emptyLibc + '/' + ldata.releases.length + ' entries have empty libc (should be "none")');
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Test 3: ext has no leading dot
|
|
// ================================================================
|
|
console.log('');
|
|
console.log('=== Test 3: ext has no leading dot ===');
|
|
console.log('');
|
|
|
|
for (var ei = 0; ei < CHECK_PKGS.length; ei++) {
|
|
var epkg = CHECK_PKGS[ei];
|
|
var edata = loadReleases(CACHE_DIR, epkg);
|
|
if (!edata) {
|
|
console.log(' SKIP ' + epkg + ': no data');
|
|
continue;
|
|
}
|
|
|
|
var dotExt = 0;
|
|
for (var eri = 0; eri < edata.releases.length; eri++) {
|
|
var ext = edata.releases[eri].ext || '';
|
|
if (ext.startsWith('.')) {
|
|
dotExt++;
|
|
}
|
|
}
|
|
|
|
if (dotExt === 0) {
|
|
console.log(' PASS ' + epkg + ': no leading dots on ext');
|
|
passes++;
|
|
} else {
|
|
console.log(' FAIL ' + epkg + ': ' + dotExt + '/' + edata.releases.length + ' entries have leading dot on ext');
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Test 4: bare binaries have ext "exe" (not empty)
|
|
// ================================================================
|
|
console.log('');
|
|
console.log('=== Test 4: bare binaries have ext "exe" ===');
|
|
console.log('');
|
|
|
|
var barePkgs = ['jq', 'bat', 'rg', 'caddy'];
|
|
for (var bi = 0; bi < barePkgs.length; bi++) {
|
|
var bpkg = barePkgs[bi];
|
|
var bdata = loadReleases(CACHE_DIR, bpkg);
|
|
if (!bdata) {
|
|
console.log(' SKIP ' + bpkg + ': no data');
|
|
continue;
|
|
}
|
|
|
|
var emptyExt = 0;
|
|
for (var bri = 0; bri < bdata.releases.length; bri++) {
|
|
var brel = bdata.releases[bri];
|
|
if (brel.ext === '' && brel.name && !brel.name.includes('.')) {
|
|
emptyExt++;
|
|
}
|
|
}
|
|
|
|
if (emptyExt === 0) {
|
|
console.log(' PASS ' + bpkg + ': no bare binaries with empty ext');
|
|
passes++;
|
|
} else {
|
|
console.log(' FAIL ' + bpkg + ': ' + emptyExt + ' bare binaries have empty ext (should be "exe")');
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Test 5: Top-level summary arrays present
|
|
// ================================================================
|
|
console.log('');
|
|
console.log('=== Test 5: Summary arrays (oses, arches, libcs, formats) ===');
|
|
console.log('');
|
|
|
|
for (var si = 0; si < CHECK_PKGS.length; si++) {
|
|
var spkg = CHECK_PKGS[si];
|
|
var sdata = loadReleases(CACHE_DIR, spkg);
|
|
if (!sdata) {
|
|
console.log(' SKIP ' + spkg + ': no data');
|
|
continue;
|
|
}
|
|
|
|
var missing = [];
|
|
if (!Array.isArray(sdata.oses)) { missing.push('oses'); }
|
|
if (!Array.isArray(sdata.arches)) { missing.push('arches'); }
|
|
if (!Array.isArray(sdata.libcs)) { missing.push('libcs'); }
|
|
if (!Array.isArray(sdata.formats)) { missing.push('formats'); }
|
|
|
|
if (missing.length === 0) {
|
|
// Verify they contain the right values
|
|
var hasMacos = sdata.oses.includes('macos') || sdata.oses.includes('darwin');
|
|
var hasLinux = sdata.oses.includes('linux');
|
|
var ok = true;
|
|
if (sdata.releases.some(function (r) { return r.os === 'macos' || r.os === 'darwin'; }) && !hasMacos) {
|
|
console.log(' FAIL ' + spkg + ': oses array missing macos/darwin');
|
|
failures++;
|
|
ok = false;
|
|
}
|
|
if (sdata.releases.some(function (r) { return r.os === 'linux'; }) && !hasLinux) {
|
|
console.log(' FAIL ' + spkg + ': oses array missing linux');
|
|
failures++;
|
|
ok = false;
|
|
}
|
|
if (ok) {
|
|
console.log(' PASS ' + spkg + ': has oses, arches, libcs, formats');
|
|
passes++;
|
|
}
|
|
} else {
|
|
console.log(' FAIL ' + spkg + ': missing top-level arrays: ' + missing.join(', '));
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Test 6: Version sort order — stable before beta, newest first
|
|
// ================================================================
|
|
console.log('');
|
|
console.log('=== Test 6: Version sort order ===');
|
|
console.log('');
|
|
|
|
var sortPkgs = ['go', 'node', 'terraform'];
|
|
for (var oi = 0; oi < sortPkgs.length; oi++) {
|
|
var opkg = sortPkgs[oi];
|
|
var odata = loadReleases(CACHE_DIR, opkg);
|
|
if (!odata) {
|
|
console.log(' SKIP ' + opkg + ': no data');
|
|
continue;
|
|
}
|
|
|
|
// Find first stable release
|
|
var firstStable = odata.releases.find(function (r) { return r.channel === 'stable'; });
|
|
// Find first beta release
|
|
var firstBeta = odata.releases.find(function (r) { return r.channel === 'beta'; });
|
|
|
|
if (!firstStable) {
|
|
console.log(' SKIP ' + opkg + ': no stable release');
|
|
continue;
|
|
}
|
|
|
|
// The first entry overall should be a stable release (newest stable > any beta)
|
|
// unless the beta is a newer version number
|
|
var firstEntry = odata.releases[0];
|
|
if (firstEntry.channel === 'stable') {
|
|
console.log(' PASS ' + opkg + ': first entry is stable (' + firstEntry.version + ')');
|
|
passes++;
|
|
} else {
|
|
console.log(' FAIL ' + opkg + ': first entry is ' + firstEntry.channel + ' (' + firstEntry.version + '), expected stable (' + firstStable.version + ')');
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Summary
|
|
// ================================================================
|
|
console.log('');
|
|
console.log('=== Results: ' + passes + ' passed, ' + failures + ' failed ===');
|
|
if (failures > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch(function (err) {
|
|
console.error(err.stack);
|
|
process.exit(1);
|
|
});
|