mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
158 lines
6.7 KiB
TypeScript
158 lines
6.7 KiB
TypeScript
import {
|
|
hostVersion,
|
|
normalizedHost,
|
|
isValidTrekRange,
|
|
minTrekOf,
|
|
hostSatisfies,
|
|
trekRangeBypassed,
|
|
bypassedRange,
|
|
__resetHostWarningForTests,
|
|
} from '../../../src/nest/plugins/install/host-compat';
|
|
|
|
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
|
|
/**
|
|
* The TREK-version gate (#plugins). Every install front door and the activation gate
|
|
* answer "can this plugin run here?" through these functions, so their edges are the
|
|
* edges of the whole feature.
|
|
*/
|
|
|
|
const APP_VERSION = process.env.APP_VERSION;
|
|
afterEach(() => {
|
|
if (APP_VERSION === undefined) delete process.env.APP_VERSION;
|
|
else process.env.APP_VERSION = APP_VERSION;
|
|
delete process.env.TREK_PLUGINS_IGNORE_TREK_RANGE;
|
|
__resetHostWarningForTests();
|
|
});
|
|
|
|
describe('isValidTrekRange', () => {
|
|
it('accepts the ranges a plugin actually declares', () => {
|
|
for (const r of ['>=3.2.0 <4.0.0', '^3.2.0', '~3.2', '>=3', '<4.0.0', '3.x', '*']) {
|
|
expect(isValidTrekRange(r), r).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('rejects a range no version can ever satisfy — validRange() alone would accept it', () => {
|
|
// Syntactically fine, semantically empty. A plugin declaring this would be
|
|
// uninstallable everywhere with nothing to tell its author why.
|
|
expect(isValidTrekRange('>=4.0.0 <3.0.0')).toBe(false);
|
|
});
|
|
|
|
it('rejects junk without letting semver.minVersion throw out of it', () => {
|
|
for (const r of ['latest', 'v3-ish', '3.2+', '', ' ', undefined, null, 42, {}]) {
|
|
expect(isValidTrekRange(r), String(r)).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('minTrekOf', () => {
|
|
it('reads the lower bound off the RANGE, not off the first version-shaped substring', () => {
|
|
// The regression this replaces: the old regex took the first X.Y.Z in the string, so
|
|
// "<4.0.0" reported a *minimum* of 4.0.0 — the exact inverse of what the plugin says.
|
|
expect(minTrekOf('<4.0.0')).toBeUndefined(); // lower bound is 0.0.0 → nothing to show
|
|
expect(minTrekOf('>=3.2.0 <4.0.0')).toBe('3.2.0');
|
|
expect(minTrekOf('^3.2.0')).toBe('3.2.0');
|
|
expect(minTrekOf('~3.2')).toBe('3.2.0');
|
|
expect(minTrekOf('>=3')).toBe('3.0.0');
|
|
expect(minTrekOf('>=3.2.0-beta.1')).toBe('3.2.0-beta.1');
|
|
});
|
|
|
|
it('has no lower bound to show for an unbounded range', () => {
|
|
expect(minTrekOf('*')).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('hostSatisfies', () => {
|
|
it('admits a host inside the range and refuses one outside it', () => {
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.3.0')).toBe(true);
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.2.0')).toBe(true);
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.1.9')).toBe(false);
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '4.0.0')).toBe(false); // the exclusive upper bound
|
|
});
|
|
|
|
it('treats a prerelease host as its target release — both edges matter', () => {
|
|
// Plain semver would fail 3.4.0-rc.1 against a range that names no prerelease, which
|
|
// would make EVERY plugin incompatible the moment TREK shipped an rc.
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.4.0-rc.1')).toBe(true);
|
|
// And includePrerelease would order 4.0.0-rc.1 BEFORE 4.0.0, so it would satisfy
|
|
// "<4.0.0" — loading a plugin that disclaims TREK 4 onto a build that already carries
|
|
// TREK 4's breaking changes. An rc of 4.0 IS 4.0 for compatibility purposes.
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '4.0.0-rc.1')).toBe(false);
|
|
expect(hostSatisfies('>=4.0.0', '4.0.0-rc.1')).toBe(true);
|
|
});
|
|
|
|
it('never blocks when the host version is not semver at all (APP_VERSION=dev)', () => {
|
|
// The Docker build ARG defaults to the literal "dev". An unversioned build must stay
|
|
// fully usable rather than refusing every plugin it has.
|
|
process.env.APP_VERSION = 'dev';
|
|
expect(normalizedHost()).toBeNull();
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0')).toBe(true);
|
|
expect(hostSatisfies(null)).toBe(true);
|
|
});
|
|
|
|
it('warns once — and only once — about an unparseable host, so a misconfig is visible', () => {
|
|
process.env.APP_VERSION = 'nonsense';
|
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
normalizedHost();
|
|
normalizedHost();
|
|
expect(warn).toHaveBeenCalledTimes(1);
|
|
expect(warn.mock.calls[0][0]).toMatch(/not a semver version/);
|
|
warn.mockRestore();
|
|
});
|
|
|
|
it('refuses an undeclared or unsatisfiable range rather than waving it through', () => {
|
|
// The caller decides what an absent range MEANS (reject at install, "unknown" at
|
|
// activation) — it must not pass by accident here.
|
|
expect(hostSatisfies(null, '3.3.0')).toBe(false);
|
|
expect(hostSatisfies('', '3.3.0')).toBe(false);
|
|
expect(hostSatisfies('>=4.0.0 <3.0.0', '3.3.0')).toBe(false);
|
|
});
|
|
|
|
it('reads the running version from APP_VERSION, falling back to the package version', () => {
|
|
process.env.APP_VERSION = '3.9.1';
|
|
expect(hostVersion()).toBe('3.9.1');
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0')).toBe(true);
|
|
expect(hostSatisfies('>=4.0.0')).toBe(false);
|
|
|
|
delete process.env.APP_VERSION;
|
|
expect(hostVersion()).toMatch(/^\d+\.\d+\.\d+/); // package.json
|
|
});
|
|
});
|
|
|
|
/**
|
|
* TREK_PLUGINS_IGNORE_TREK_RANGE: an operator's explicit "install it anyway" for a plugin
|
|
* whose author has not updated its `trek` range. The gates warn instead of refusing, but
|
|
* the truth (hostSatisfies) never changes — the UI still has to say the plugin is
|
|
* outside its range.
|
|
*/
|
|
describe('range bypass (TREK_PLUGINS_IGNORE_TREK_RANGE)', () => {
|
|
it('is off by default and only an explicit truthy value turns it on', () => {
|
|
expect(trekRangeBypassed()).toBe(false);
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '0';
|
|
expect(trekRangeBypassed()).toBe(false);
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '1';
|
|
expect(trekRangeBypassed()).toBe(true);
|
|
});
|
|
|
|
it('reports a bypassed mismatch only when the bypass is on AND the range does not admit the host', () => {
|
|
process.env.APP_VERSION = '3.3.0';
|
|
expect(bypassedRange('>=4.0.0')).toBeNull(); // off → the gate refuses, nothing to report
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = 'true';
|
|
expect(bypassedRange('>=4.0.0')).toEqual({ trekRange: '>=4.0.0', hostVersion: '3.3.0' });
|
|
expect(bypassedRange('>=3.2.0 <4.0.0')).toBeNull(); // fits → not a bypass, a normal install
|
|
});
|
|
|
|
it('covers a plugin that declared no range at all — the same author oversight', () => {
|
|
process.env.APP_VERSION = '3.3.0';
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '1';
|
|
expect(bypassedRange(null)).toEqual({ trekRange: null, hostVersion: '3.3.0' });
|
|
expect(bypassedRange(undefined)).toEqual({ trekRange: null, hostVersion: '3.3.0' });
|
|
});
|
|
|
|
it('leaves hostSatisfies telling the truth while bypassed', () => {
|
|
process.env.APP_VERSION = '3.3.0';
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '1';
|
|
expect(hostSatisfies('>=4.0.0')).toBe(false);
|
|
});
|
|
});
|