DC-035: add regression test for SelfUpdater.getLocalVersion() (DC-033 class)
Adds 6 tests that catch the exact bug DC-033 fixed. Verified to actually fail (4/6) against the pre-fix code (git show 20d280f^:self-updater.js), proving it's a real regression test and not a placebo. Full suite: 40/40 suites, 1081/1081 tests.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Regression tests for getLocalVersion() — DC-033.
|
||||
*
|
||||
* The SelfUpdater's getLocalVersion() reads package.json + VERSION from the
|
||||
* filesystem relative to its own __dirname. server.js loads it via
|
||||
* `./src/docker/self-updater`, so __dirname inside the container is
|
||||
* `/app/src/docker` — which has no package.json. The function's outer
|
||||
* try/catch silently swallowed the ENOENT and returned the
|
||||
* `{ version: '0.0.0', commit: null }` fallback, making every DashCaddy
|
||||
* host running v1.14.x (≤ v1.14.8) appear to be at "version 0.0.0" in the
|
||||
* dashboard and "always outdated" to checkForUpdate().
|
||||
*
|
||||
* DC-033 fixed it by walking a candidate list (api root first, __dirname
|
||||
* second). DC-035 is the regression test: if anyone re-introduces the
|
||||
* __dirname antipattern — or accidentally deletes the api-root package.json
|
||||
* — this suite will fail loudly.
|
||||
*
|
||||
* Loading pattern matters: this test loads `./src/docker/self-updater` to
|
||||
* match what server.js does at runtime. The legacy `./self-updater` path
|
||||
* (from /app) was deleted by DC-036, so the only require() that exists
|
||||
* now is the docker copy.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
describe('SelfUpdater.getLocalVersion() — DC-033 regression', () => {
|
||||
// Resolve from a known cwd so require('./src/docker/self-updater') lands
|
||||
// on the api-root copy, not some other relative-resolution target.
|
||||
const API_ROOT = path.join(__dirname, '..');
|
||||
let SelfUpdater;
|
||||
|
||||
beforeAll(() => {
|
||||
// Sanity check: the file must exist at the expected path.
|
||||
const target = path.join(API_ROOT, 'src', 'docker', 'self-updater.js');
|
||||
expect(() => require.resolve(target)).not.toThrow();
|
||||
|
||||
const mod = require(path.join(API_ROOT, 'src', 'docker', 'self-updater.js'));
|
||||
SelfUpdater = mod.SelfUpdater || mod.default || mod;
|
||||
});
|
||||
|
||||
test('module loads and exports a SelfUpdater class', () => {
|
||||
expect(typeof SelfUpdater).toBe('function');
|
||||
expect(SelfUpdater.name).toBe('SelfUpdater');
|
||||
});
|
||||
|
||||
describe('getLocalVersion() returns real version + commit', () => {
|
||||
let result;
|
||||
|
||||
beforeAll(() => {
|
||||
// Empty options — DEFAULTS will be used; getLocalVersion doesn't
|
||||
// need config to read sibling files.
|
||||
const instance = new SelfUpdater({});
|
||||
result = instance.getLocalVersion();
|
||||
});
|
||||
|
||||
test('result is an object with version + commit', () => {
|
||||
expect(result).toEqual(expect.objectContaining({
|
||||
version: expect.any(String),
|
||||
commit: expect.any(String),
|
||||
}));
|
||||
});
|
||||
|
||||
test('version is NOT the 0.0.0 fallback (the DC-033 bug)', () => {
|
||||
// If this fails, someone re-introduced the __dirname antipattern.
|
||||
expect(result.version).not.toBe('0.0.0');
|
||||
});
|
||||
|
||||
test('version is a valid semver string', () => {
|
||||
// Anchored semver: MAJOR.MINOR.PATCH with optional pre-release/build.
|
||||
// Reject '0.0.0' explicitly and anything without 3 numeric components.
|
||||
expect(result.version).toMatch(/^\d+\.\d+\.\d+/);
|
||||
const parts = result.version.split('.');
|
||||
expect(parts.length).toBeGreaterThanOrEqual(3);
|
||||
for (const part of parts) {
|
||||
// Allow pre-release suffixes (e.g. "1-rc1") but the first 3 must be numeric.
|
||||
const numeric = part.split('-')[0].split('+')[0];
|
||||
expect(numeric).toMatch(/^\d+$/);
|
||||
}
|
||||
});
|
||||
|
||||
test('commit is a git SHA (7-40 hex chars), not null', () => {
|
||||
expect(result.commit).not.toBeNull();
|
||||
expect(result.commit).toMatch(/^[0-9a-f]{7,40}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('candidate-path resolution survives missing sibling files', () => {
|
||||
// If we shadow __dirname by requiring the module through a different
|
||||
// require() chain, the function should still find package.json via its
|
||||
// candidate-list fallback. This catches the case where someone refactors
|
||||
// the file to a deeper subdirectory and forgets to update the candidates.
|
||||
test('getLocalVersion works regardless of how the module is required', () => {
|
||||
const mod = require(path.join(API_ROOT, 'src', 'docker', 'self-updater.js'));
|
||||
const Cls = mod.SelfUpdater || mod.default || mod;
|
||||
const result = new Cls({}).getLocalVersion();
|
||||
expect(result.version).not.toBe('0.0.0');
|
||||
expect(result.commit).toMatch(/^[0-9a-f]{7,40}$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user