Files
dashcaddy/dashcaddy-api/__tests__/license-manager.test.js
T
Hermes a468e0f480 [grade=B] DC-083: comprehensive license-manager.js test coverage (77 tests, revenue path)
Added __tests__/license-manager.test.js with 77 tests covering the entire
src/managers/license-manager.js module (534 LOC) — the revenue validation
path that was previously untested by any dedicated test file.

Coverage includes:
- load(): credential-store primary, config-backup fallback, no-license,
  credential-store error → config recovery, re-store after restore
- activate(): real crypto round-trip for all durations (30/90/180/365),
  already-activated idempotency, invalid format, missing code, offline
  HMAC validation failure, LIFETIME rejection (prod) + acceptance (dev),
  credential-store save failure, config write, lowercase normalization,
  whitespace trimming
- activate() online path: server success, server unreachable → offline
  fallback, server explicit rejection (no fallback)
- deactivate(): success, no-active-license, credential delete, config clear
- getStatus(): free tier, active premium, expired, lifetime, code masking
- hasFeature(): no-activation, active, expired, specific-feature, default
- isPro()/isExpired()/daysRemaining(): all branches (no-activation, active,
  expired, lifetime, missing expiresAt)
- getMachineFingerprint(): stable 16-char hex
- requirePremium() middleware: next() on available, 403 on unavailable,
  upgrade URL, unknown feature
- loadSecret(): file-exists, file-missing, read-error (deterministic fs mock)
- _validateOffline(): with-secret valid, forged HMAC mismatch, no-secret
  structural-only, malformed code, unsupported version (forged v2 payload)
- _updateConfig(): creates config, preserves fields, clears on deactivation,
  nonexistent-directory tolerance
- _maskCode(): standard, short, empty
- Full lifecycle: activate→status→deactivate→status, load-after-activate
  restore, freshly-minted-code validation

Unlike license-tier-enforcement.test.js (which stubs _validateOffline),
these tests exercise the REAL crypto flow end-to-end: generateCode(TEST_SECRET)
→ activate(code) → _validateOffline(code) → verifyCode(secret, code) →
credential store. Uses jest.isolateModules for online tests so the module-
level LICENSE_SERVER_URL const is re-read per test.

Codex grade: B (urn:ump:vwial6vhrzzmsvpfjdxnk53hvol3wna3o2zwmneqjcquxfgdersq)
Full suite: 1738/1738 pass (was 1661, +77 new). Zero new ESLint warnings on src/.
2026-08-12 16:11:15 -07:00

1491 lines
53 KiB
JavaScript

/**
* Comprehensive tests for src/managers/license-manager.js
*
* This is the revenue validation path — an untested bug here could silently
* break activation for every paying customer (DC-083 priority #1).
*
* Unlike license-tier-enforcement.test.js (which stubs _validateOffline and
* only tests the isPro/tier-gating surface), these tests exercise the REAL
* crypto flow end-to-end: generateCode(TEST_SECRET, ...) → activate(code) →
* _validateOffline(code) → verifyCode(secret, code) → credential store.
*
* Coverage matrix:
* - constructor + load(): credential-store primary, config-backup fallback,
* no-license, credential-store error → config recovery, re-store
* - activate(): real-code round-trip (30/90/180/365), already-activated
* idempotency, invalid format, missing code, offline-validation failure,
* LIFETIME rejection (prod) + acceptance (dev env), credential-store
* save failure, config write
* - activate() online path: server success, server unreachable → offline
* fallback, server explicit rejection (no fallback)
* - deactivate(): success, no-active-license, credential delete
* - getStatus(): free tier, active premium, expired, lifetime
* - hasFeature(): no-activation, active, expired, specific-feature
* - isPro() / isExpired() / daysRemaining(): all branches
* - getMachineFingerprint(): stable, hex format
* - requirePremium() middleware: feature-available (next), feature-unavailable (403)
* - loadSecret(): file-exists, file-missing, file-unreadable
* - _validateOffline(): with-secret, no-secret structural-only, invalid-code,
* unsupported-version (forged v2 payload)
* - _updateConfig(): writes license + backup, clears on deactivation
* - _maskCode(): standard, short, empty
* - Full lifecycle: activate → status → deactivate, load-after-activate restore
*/
'use strict';
const fs = require('fs');
const path = require('path');
const os = require('os');
const crypto = require('crypto');
// Use the real keygen to generate cryptographically valid codes.
const { generateCode, verifyCode } = require('../license-keygen');
const TEST_SECRET = 'a'.repeat(64); // 32 bytes hex — deterministic test secret
function _tmpDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'dc-lm-test-'));
}
function _cleanup(dir) {
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_) { /* best effort */ }
}
/**
* Build a LicenseManager with a fresh module instance + env override.
* Returns { mgr, restore, dir }.
*
* @param {Object} opts
* @param {Object} opts.creds — credential store stub {store, retrieve, delete}
* @param {Object} opts.env — process.env overrides (e.g. LICENSE_SERVER_URL)
* @param {string} opts.secret — master secret to load via loadSecret()
*/
function _makeManager(opts = {}) {
const { creds = {}, env = {}, secret = null } = opts;
const prevEnv = { ...process.env };
// Object.assign copies undefined-valued keys as the string "undefined".
// Delete env keys whose value is undefined so the production "unset" path
// is exercised accurately.
for (const [k, v] of Object.entries(env)) {
if (v === undefined) {
delete process.env[k];
} else {
process.env[k] = v;
}
}
// Force re-require so LICENSE_SERVER_URL is re-read.
delete require.cache[require.resolve('../src/managers/license-manager')];
const { LicenseManager } = require('../src/managers/license-manager');
const dir = _tmpDir();
const configFile = path.join(dir, 'config.json');
const secretFile = path.join(dir, '.license-secret');
const defaultCreds = {
_store: {},
async store(key, val) { this._store[key] = val; },
async retrieve(key) { return this._store[key] || null; },
async delete(key) { delete this._store[key]; },
};
const mgr = new LicenseManager(
creds._impl ? creds : defaultCreds,
configFile,
{ info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }
);
if (secret) {
fs.writeFileSync(secretFile, secret, 'utf8');
mgr.loadSecret(secretFile);
}
const restore = async () => {
process.env = prevEnv;
_cleanup(dir);
};
return { mgr, restore, dir, configFile, secretFile };
}
// ── generateCode helper: mint a real valid code for a given duration ──────
function _mintCode(secret, durationDays, codeId = 1) {
return generateCode(secret, durationDays, codeId);
}
// ===========================================================================
// constructor + load()
// ===========================================================================
describe('LicenseManager: load()', () => {
test('loads active license from credential store', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
// Pre-populate the credential store with a valid activation
const code = _mintCode(TEST_SECRET, 30, 1);
const activation = {
code,
codeId: 1,
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso', 'recipes', 'swarm'],
};
await mgr.credentialManager.store('license.activation', JSON.stringify(activation));
await mgr.load();
expect(mgr.activation).toBeTruthy();
expect(mgr.activation.code).toBe(code);
expect(mgr._loaded).toBe(true);
expect(mgr.isPro()).toBe(true);
} finally { await restore(); }
});
test('logs expired license on load but keeps it', async () => {
const { mgr, restore } = _makeManager();
try {
const activation = {
code: 'DC-TEST-EXPIRED',
durationDays: 30,
lifetime: false,
activatedAt: '2020-01-01T00:00:00.000Z',
expiresAt: '2020-02-01T00:00:00.000Z',
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
await mgr.credentialManager.store('license.activation', JSON.stringify(activation));
await mgr.load();
expect(mgr.activation).toBeTruthy();
expect(mgr.isExpired()).toBe(true);
expect(mgr._loaded).toBe(true);
} finally { await restore(); }
});
test('falls back to config.json licenseBackup when credential store fails', async () => {
const dir = _tmpDir();
try {
const configFile = path.join(dir, 'config.json');
const activation = {
code: 'DC-BACKUP-TEST',
durationDays: 90,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 90 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
fs.writeFileSync(configFile, JSON.stringify({ licenseBackup: activation }, null, 2));
// Credential store that throws on retrieve
const failCreds = {
async retrieve() { throw new Error('encryption key changed'); },
async store() {},
async delete() {},
};
delete require.cache[require.resolve('../src/managers/license-manager')];
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager(failCreds, configFile, { info: () => {}, warn: () => {} });
await mgr.load();
expect(mgr.activation).toBeTruthy();
expect(mgr.activation.code).toBe('DC-BACKUP-TEST');
expect(mgr._loaded).toBe(true);
} finally { _cleanup(dir); }
});
test('re-stores recovered license in credential manager after config backup restore', async () => {
const dir = _tmpDir();
try {
const configFile = path.join(dir, 'config.json');
const activation = {
code: 'DC-RESTORE-TEST',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
fs.writeFileSync(configFile, JSON.stringify({ licenseBackup: activation }, null, 2));
const storeCalls = [];
const failCreds = {
async retrieve() { throw new Error('decryption failed'); },
async store(key, val) { storeCalls.push({ key, val }); },
async delete() {},
};
delete require.cache[require.resolve('../src/managers/license-manager')];
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager(failCreds, configFile, { info: () => {}, warn: () => {} });
await mgr.load();
expect(storeCalls.length).toBe(1);
expect(storeCalls[0].key).toBe('license.activation');
expect(JSON.parse(storeCalls[0].val).code).toBe('DC-RESTORE-TEST');
} finally { _cleanup(dir); }
});
test('sets activation=null when no license anywhere', async () => {
const { mgr, restore } = _makeManager();
try {
await mgr.load();
expect(mgr.activation).toBeNull();
expect(mgr._loaded).toBe(true);
} finally { await restore(); }
});
test('sets activation=null when config.json has no licenseBackup', async () => {
const dir = _tmpDir();
try {
const configFile = path.join(dir, 'config.json');
fs.writeFileSync(configFile, JSON.stringify({ someOtherField: true }, null, 2));
const emptyCreds = {
async retrieve() { return null; },
async store() {},
async delete() {},
};
delete require.cache[require.resolve('../src/managers/license-manager')];
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager(emptyCreds, configFile, { info: () => {} });
await mgr.load();
expect(mgr.activation).toBeNull();
} finally { _cleanup(dir); }
});
});
// ===========================================================================
// activate() — the core revenue path
// ===========================================================================
describe('LicenseManager: activate() — real crypto round-trip', () => {
test('activates a valid 30-day code via offline HMAC validation', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 100);
const result = await mgr.activate(code);
expect(result.success).toBe(true);
expect(result.message).toMatch(/30 days/);
expect(result.activation).toBeTruthy();
expect(result.activation.active).toBe(true);
expect(result.activation.tier).toBe('premium');
expect(result.activation.durationDays).toBe(30);
expect(result.activation.lifetime).toBe(false);
expect(result.activation.daysRemaining).toBeGreaterThan(29);
expect(result.activation.daysRemaining).toBeLessThanOrEqual(30);
} finally { await restore(); }
});
test('activates valid 90, 180, and 365-day codes', async () => {
for (const duration of [90, 180, 365]) {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, duration, 200 + duration);
const result = await mgr.activate(code);
expect(result.success).toBe(true);
expect(result.activation.durationDays).toBe(duration);
expect(result.activation.daysRemaining).toBeGreaterThan(duration - 1);
} finally { await restore(); }
}
});
test('persists activation to credential store after successful activation', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 300);
await mgr.activate(code);
const stored = await mgr.credentialManager.retrieve('license.activation');
expect(stored).toBeTruthy();
const parsed = JSON.parse(stored);
expect(parsed.code).toBe(code);
expect(parsed.durationDays).toBe(30);
} finally { await restore(); }
});
test('writes config.json with license info + backup after activation', async () => {
const { mgr, restore, configFile } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 90, 400);
await mgr.activate(code);
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
expect(config.license.active).toBe(true);
expect(config.license.tier).toBe('premium');
expect(config.licenseBackup).toBeTruthy();
expect(config.licenseBackup.code).toBe(code);
} finally { await restore(); }
});
test('idempotent: activating the same valid code twice returns already-activated', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 500);
const result1 = await mgr.activate(code);
expect(result1.success).toBe(true);
const result2 = await mgr.activate(code);
expect(result2.success).toBe(true);
expect(result2.message).toMatch(/already activated/i);
} finally { await restore(); }
});
test('rejects empty/null/undefined code', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
expect((await mgr.activate('')).success).toBe(false);
expect((await mgr.activate(null)).success).toBe(false);
expect((await mgr.activate(undefined)).success).toBe(false);
expect((await mgr.activate(12345)).success).toBe(false);
} finally { await restore(); }
});
test('rejects code without DC- prefix', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const result = await mgr.activate('XYZ-ABCDE-FGHIJ-KLMNO-PQRST-UVWXY');
expect(result.success).toBe(false);
expect(result.message).toMatch(/DC-/);
} finally { await restore(); }
});
test('rejects code with invalid HMAC signature (all-A base32, forged)', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const result = await mgr.activate('DC-AAAAA-AAAAA-AAAAA-AAAAA-AAAAA');
// Structurally valid base32 (all 'A') parses fine but the HMAC computed
// from the decoded payload won't match — offline validation fails.
expect(result.success).toBe(false);
} finally { await restore(); }
});
test('rejects code with wrong HMAC signature (forged)', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
// Generate with a DIFFERENT secret, try to activate with TEST_SECRET
const wrongSecret = 'b'.repeat(64);
const forgedCode = _mintCode(wrongSecret, 30, 600);
const result = await mgr.activate(forgedCode);
expect(result.success).toBe(false);
expect(result.message).toMatch(/invalid|forged|corrupted/i);
} finally { await restore(); }
});
test('LIFETIME code is rejected in production (ALLOW_LIFETIME_LICENSE unset)', async () => {
const { mgr, restore } = _makeManager({
secret: TEST_SECRET,
env: { ALLOW_LIFETIME_LICENSE: undefined },
});
try {
const lifetimeCode = _mintCode(TEST_SECRET, 0, 700); // durationDays=0 → lifetime
const result = await mgr.activate(lifetimeCode);
expect(result.success).toBe(false);
expect(result.message).toMatch(/lifetime/i);
expect(result.message).toMatch(/not available/i);
expect(mgr.activation).toBeNull();
} finally { await restore(); }
});
test('LIFETIME code is accepted when ALLOW_LIFETIME_LICENSE=true', async () => {
const { mgr, restore } = _makeManager({
secret: TEST_SECRET,
env: { ALLOW_LIFETIME_LICENSE: 'true' },
});
try {
const lifetimeCode = _mintCode(TEST_SECRET, 0, 800);
const result = await mgr.activate(lifetimeCode);
expect(result.success).toBe(true);
expect(result.activation.lifetime).toBe(true);
expect(result.activation.tier).toBe('premium');
expect(result.activation.expiresAt).toBeNull();
expect(result.activation.daysRemaining).toBeNull();
expect(mgr.isPro()).toBe(true);
} finally { await restore(); }
});
test('returns failure when credential store throws on save', async () => {
const failCreds = {
async store() { throw new Error('disk full'); },
async retrieve() { return null; },
async delete() {},
};
const { mgr, restore } = _makeManager({
secret: TEST_SECRET,
creds: { _impl: true, ...failCreds },
});
try {
const code = _mintCode(TEST_SECRET, 30, 900);
const result = await mgr.activate(code);
expect(result.success).toBe(false);
expect(result.message).toMatch(/failed to save/i);
} finally { await restore(); }
});
test('normalizes lowercase code to uppercase', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 1000);
const result = await mgr.activate(code.toLowerCase());
expect(result.success).toBe(true);
expect(mgr.activation.code).toBe(code); // stored uppercase
} finally { await restore(); }
});
test('trims whitespace around code', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 1100);
const result = await mgr.activate(` ${code} `);
expect(result.success).toBe(true);
} finally { await restore(); }
});
});
// ===========================================================================
// activate() — online validation path
//
// LICENSE_SERVER_URL is a module-level const read at require() time, so we
// use jest.isolateModules to get a fresh module instance with the env var
// set. The credential manager stub is created inside the isolation callback.
// ===========================================================================
describe('LicenseManager: activate() — online validation', () => {
test('uses online server when LICENSE_SERVER_URL is set and returns success', async () => {
const originalFetch = global.fetch;
const dir = _tmpDir();
const prevUrl = process.env.LICENSE_SERVER_URL;
process.env.LICENSE_SERVER_URL = 'https://license.test.example';
try {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({
success: true,
codeId: 999,
durationDays: 365,
expiresAt: new Date(Date.now() + 365 * 86400000).toISOString(),
features: ['sso', 'recipes', 'swarm'],
token: 'srv-token-abc',
}),
});
const creds = {
_store: {},
async store(key, val) { this._store[key] = val; },
async retrieve(key) { return this._store[key] || null; },
async delete(key) { delete this._store[key]; },
};
let result;
jest.isolateModules(() => {
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager(creds, path.join(dir, 'config.json'), { info: () => {}, warn: () => {}, error: () => {} });
// Run synchronously — activate is async but we capture the promise
result = mgr.activate('DC-ABCDE-FGHIJ-KLMNO-PQRST-UVWXY');
});
const res = await result;
expect(res.success).toBe(true);
expect(res.activation.validationMethod).toBe('online');
expect(global.fetch).toHaveBeenCalledWith(
'https://license.test.example/api/license/validate',
expect.objectContaining({ method: 'POST' })
);
} finally {
if (prevUrl === undefined) delete process.env.LICENSE_SERVER_URL;
else process.env.LICENSE_SERVER_URL = prevUrl;
global.fetch = originalFetch;
_cleanup(dir);
}
});
test('falls back to offline when server is unreachable (fetch throws)', async () => {
const originalFetch = global.fetch;
const dir = _tmpDir();
const prevUrl = process.env.LICENSE_SERVER_URL;
process.env.LICENSE_SERVER_URL = 'https://license.test.example';
try {
global.fetch = jest.fn().mockRejectedValue(new Error('ECONNREFUSED'));
const secretFile = path.join(dir, '.license-secret');
fs.writeFileSync(secretFile, TEST_SECRET, 'utf8');
const creds = {
_store: {},
async store(key, val) { this._store[key] = val; },
async retrieve(key) { return this._store[key] || null; },
async delete(key) { delete this._store[key]; },
};
let result;
jest.isolateModules(() => {
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager(creds, path.join(dir, 'config.json'), { info: () => {}, warn: () => {}, error: () => {} });
mgr.loadSecret(secretFile);
const code = _mintCode(TEST_SECRET, 30, 1200);
result = mgr.activate(code);
});
const res = await result;
expect(res.success).toBe(true);
expect(res.activation.validationMethod).toBe('offline');
} finally {
if (prevUrl === undefined) delete process.env.LICENSE_SERVER_URL;
else process.env.LICENSE_SERVER_URL = prevUrl;
global.fetch = originalFetch;
_cleanup(dir);
}
});
test('rejects when server explicitly returns failure (no fallback)', async () => {
const originalFetch = global.fetch;
const dir = _tmpDir();
const prevUrl = process.env.LICENSE_SERVER_URL;
process.env.LICENSE_SERVER_URL = 'https://license.test.example';
try {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
status: 403,
json: async () => ({ error: 'Code revoked by administrator' }),
});
const creds = {
_store: {},
async store(key, val) { this._store[key] = val; },
async retrieve(key) { return this._store[key] || null; },
async delete(key) { delete this._store[key]; },
};
let result;
jest.isolateModules(() => {
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager(creds, path.join(dir, 'config.json'), { info: () => {}, warn: () => {}, error: () => {} });
// Use a code with correct length (25 base32 chars = 5 groups of 5)
result = mgr.activate('DC-ABCDE-FGHIJ-KLMNO-PQRST-UVWXY');
});
const res = await result;
expect(res.success).toBe(false);
expect(res.message).toMatch(/revoked/i);
} finally {
if (prevUrl === undefined) delete process.env.LICENSE_SERVER_URL;
else process.env.LICENSE_SERVER_URL = prevUrl;
global.fetch = originalFetch;
_cleanup(dir);
}
});
});
// ===========================================================================
// deactivate()
// ===========================================================================
describe('LicenseManager: deactivate()', () => {
test('deactivates an active license', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 1300);
await mgr.activate(code);
expect(mgr.isPro()).toBe(true);
const result = await mgr.deactivate();
expect(result.success).toBe(true);
expect(result.message).toMatch(/deactivated/i);
expect(mgr.activation).toBeNull();
expect(mgr.isPro()).toBe(false);
} finally { await restore(); }
});
test('clears credential store on deactivation', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 1400);
await mgr.activate(code);
expect(await mgr.credentialManager.retrieve('license.activation')).toBeTruthy();
await mgr.deactivate();
expect(await mgr.credentialManager.retrieve('license.activation')).toBeNull();
} finally { await restore(); }
});
test('updates config.json to free tier after deactivation', async () => {
const { mgr, restore, configFile } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 1500);
await mgr.activate(code);
// Verify config has license
let config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
expect(config.license.active).toBe(true);
await mgr.deactivate();
config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
expect(config.license.active).toBe(false);
expect(config.license.tier).toBe('free');
expect(config.licenseBackup).toBeUndefined();
} finally { await restore(); }
});
test('returns failure when no active license', async () => {
const { mgr, restore } = _makeManager();
try {
const result = await mgr.deactivate();
expect(result.success).toBe(false);
expect(result.message).toMatch(/no active/i);
} finally { await restore(); }
});
});
// ===========================================================================
// getStatus()
// ===========================================================================
describe('LicenseManager: getStatus()', () => {
test('returns free tier when no activation', () => {
const { mgr, restore } = _makeManager();
try {
const status = mgr.getStatus();
expect(status.active).toBe(false);
expect(status.tier).toBe('free');
expect(status.features).toEqual([]);
expect(status.premiumFeatures).toBeTruthy();
} finally { restore(); }
});
test('returns premium tier for active license', () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
mgr.activation = {
code: 'DC-STATUS-TEST',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
const status = mgr.getStatus();
expect(status.active).toBe(true);
expect(status.tier).toBe('premium');
expect(status.features).toContain('sso');
expect(status.daysRemaining).toBeGreaterThan(29);
} finally { restore(); }
});
test('returns free tier for expired license', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-EXPIRED-STATUS',
durationDays: 30,
lifetime: false,
activatedAt: '2020-01-01T00:00:00Z',
expiresAt: '2020-02-01T00:00:00Z',
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
const status = mgr.getStatus();
expect(status.active).toBe(false);
expect(status.tier).toBe('free');
expect(status.expired).toBe(true);
expect(status.features).toEqual([]);
} finally { restore(); }
});
test('returns null expiresAt/daysRemaining for lifetime', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-LIFETIME-STATUS',
durationDays: 0,
lifetime: true,
activatedAt: new Date().toISOString(),
expiresAt: new Date('2099-12-31T23:59:59.999Z').toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso', 'recipes'],
};
const status = mgr.getStatus();
expect(status.active).toBe(true);
expect(status.lifetime).toBe(true);
expect(status.expiresAt).toBeNull();
expect(status.daysRemaining).toBeNull();
} finally { restore(); }
});
test('masks the code in status output', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-ABCDE-FGHIJ-KLMNO-PQRST-UVWXY',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
const status = mgr.getStatus();
expect(status.code).not.toBe(mgr.activation.code);
expect(status.code).toMatch(/^DC-/);
expect(status.code).toContain('*****');
} finally { restore(); }
});
});
// ===========================================================================
// hasFeature()
// ===========================================================================
describe('LicenseManager: hasFeature()', () => {
test('returns false when no activation', () => {
const { mgr, restore } = _makeManager();
try {
expect(mgr.hasFeature('sso')).toBe(false);
} finally { restore(); }
});
test('returns true for available feature on active license', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-FEATURE-TEST',
durationDays: 90,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 90 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso', 'recipes'],
};
expect(mgr.hasFeature('sso')).toBe(true);
expect(mgr.hasFeature('recipes')).toBe(true);
} finally { restore(); }
});
test('returns false for unavailable feature', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-FEATURE-TEST',
durationDays: 90,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 90 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
expect(mgr.hasFeature('swarm')).toBe(false);
} finally { restore(); }
});
test('returns false when license expired', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-EXPIRED-FEATURE',
durationDays: 30,
lifetime: false,
activatedAt: '2020-01-01T00:00:00Z',
expiresAt: '2020-02-01T00:00:00Z',
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
expect(mgr.hasFeature('sso')).toBe(false);
} finally { restore(); }
});
test('falls back to PREMIUM_FEATURES keys when activation.features missing', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-NO-FEATURES-LIST',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
// features omitted
};
// Should default to all PREMIUM_FEATURES
expect(mgr.hasFeature('sso')).toBe(true);
expect(mgr.hasFeature('recipes')).toBe(true);
expect(mgr.hasFeature('swarm')).toBe(true);
} finally { restore(); }
});
});
// ===========================================================================
// isPro() / isExpired() / daysRemaining()
// ===========================================================================
describe('LicenseManager: isPro()', () => {
test('false when no activation', () => {
const { mgr, restore } = _makeManager();
try {
expect(mgr.isPro()).toBe(false);
} finally { restore(); }
});
test('true for active non-lifetime license', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-PRO-TEST',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
expect(mgr.isPro()).toBe(true);
} finally { restore(); }
});
test('true for active lifetime license', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-LIFETIME-PRO',
durationDays: 0,
lifetime: true,
activatedAt: new Date().toISOString(),
expiresAt: new Date('2099-12-31').toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
expect(mgr.isPro()).toBe(true);
} finally { restore(); }
});
test('false for expired license', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-EXPIRED-PRO',
durationDays: 30,
lifetime: false,
activatedAt: '2020-01-01T00:00:00Z',
expiresAt: '2020-02-01T00:00:00Z',
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
expect(mgr.isPro()).toBe(false);
} finally { restore(); }
});
});
describe('LicenseManager: isExpired()', () => {
test('true when no activation', () => {
const { mgr, restore } = _makeManager();
try {
expect(mgr.isExpired()).toBe(true);
} finally { restore(); }
});
test('false for lifetime (durationDays=0)', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = { durationDays: 0, lifetime: true, expiresAt: null };
expect(mgr.isExpired()).toBe(false);
} finally { restore(); }
});
test('false for lifetime flag only (no durationDays)', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = { lifetime: true, expiresAt: '2020-01-01T00:00:00Z' };
expect(mgr.isExpired()).toBe(false);
} finally { restore(); }
});
test('false when expiresAt is null/missing (treated as lifetime)', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = { durationDays: 30, lifetime: false, expiresAt: null };
expect(mgr.isExpired()).toBe(false);
} finally { restore(); }
});
test('true when expiry is in the past', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
durationDays: 30,
lifetime: false,
expiresAt: new Date(Date.now() - 86400000).toISOString(),
};
expect(mgr.isExpired()).toBe(true);
} finally { restore(); }
});
test('false when expiry is in the future', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
durationDays: 30,
lifetime: false,
expiresAt: new Date(Date.now() + 86400000).toISOString(),
};
expect(mgr.isExpired()).toBe(false);
} finally { restore(); }
});
});
describe('LicenseManager: daysRemaining()', () => {
test('returns 0 when no activation', () => {
const { mgr, restore } = _makeManager();
try {
expect(mgr.daysRemaining()).toBe(0);
} finally { restore(); }
});
test('returns positive days for active license', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
expiresAt: new Date(Date.now() + 15 * 86400000).toISOString(),
};
const days = mgr.daysRemaining();
expect(days).toBeGreaterThanOrEqual(14);
expect(days).toBeLessThanOrEqual(15);
} finally { restore(); }
});
test('returns negative for expired license (Math.ceil of negative)', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
expiresAt: new Date(Date.now() - 5 * 86400000).toISOString(),
};
const days = mgr.daysRemaining();
expect(days).toBeLessThanOrEqual(-4);
} finally { restore(); }
});
});
// ===========================================================================
// getMachineFingerprint()
// ===========================================================================
describe('LicenseManager: getMachineFingerprint()', () => {
test('returns a 16-char hex string', () => {
const { mgr, restore } = _makeManager();
try {
const fp = mgr.getMachineFingerprint();
expect(fp).toMatch(/^[0-9a-f]{16}$/);
} finally { restore(); }
});
test('is stable across calls (same machine)', () => {
const { mgr, restore } = _makeManager();
try {
const fp1 = mgr.getMachineFingerprint();
const fp2 = mgr.getMachineFingerprint();
expect(fp1).toBe(fp2);
} finally { restore(); }
});
});
// ===========================================================================
// requirePremium() middleware
// ===========================================================================
describe('LicenseManager: requirePremium() middleware', () => {
test('calls next() when feature is available', () => {
const { mgr, restore } = _makeManager();
try {
mgr.activation = {
code: 'DC-MW-TEST',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
const middleware = mgr.requirePremium('sso');
const req = {};
const res = {};
let nextCalled = false;
middleware(req, res, (err) => { nextCalled = !err; });
expect(nextCalled).toBe(true);
} finally { restore(); }
});
test('returns 403 with premiumRequired when feature unavailable', () => {
const { mgr, restore } = _makeManager();
try {
const middleware = mgr.requirePremium('sso');
const req = {};
let status = null;
let body = null;
const res = {
status(s) { status = s; return this; },
json(b) { body = b; return this; },
};
middleware(req, res, () => {});
expect(status).toBe(403);
expect(body.success).toBe(false);
expect(body.premiumRequired).toBe(true);
expect(body.feature).toBe('sso');
} finally { restore(); }
});
test('includes upgrade URL in 403 response', () => {
const { mgr, restore } = _makeManager();
try {
const middleware = mgr.requirePremium('recipes');
const req = {};
let body = null;
const res = {
status() { return this; },
json(b) { body = b; return this; },
};
middleware(req, res, () => {});
expect(body.upgradeUrl).toMatch(/settings.*license|license.*settings/i);
} finally { restore(); }
});
test('handles unknown feature key gracefully', () => {
const { mgr, restore } = _makeManager();
try {
const middleware = mgr.requirePremium('nonexistent');
const req = {};
let body = null;
const res = {
status() { return this; },
json(b) { body = b; return this; },
};
middleware(req, res, () => {});
expect(body.featureName).toBe('nonexistent');
} finally { restore(); }
});
});
// ===========================================================================
// loadSecret()
// ===========================================================================
describe('LicenseManager: loadSecret()', () => {
test('loads secret from file and returns true', () => {
const dir = _tmpDir();
try {
const secretFile = path.join(dir, '.license-secret');
fs.writeFileSync(secretFile, 'testsecret123', 'utf8');
delete require.cache[require.resolve('../src/managers/license-manager')];
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager({}, '/nonexistent', { info: () => {} });
const result = mgr.loadSecret(secretFile);
expect(result).toBe(true);
expect(mgr.masterSecretHash).toBe('testsecret123');
} finally { _cleanup(dir); }
});
test('returns false when secret file does not exist', () => {
const { mgr, restore } = _makeManager();
try {
const result = mgr.loadSecret('/nonexistent/secret/path');
expect(result).toBe(false);
expect(mgr.masterSecretHash).toBeNull();
} finally { restore(); }
});
test('returns false and logs warning when secret file read throws', () => {
const dir = _tmpDir();
// Mock fs.existsSync + fs.readFileSync; restore in finally so any
// failure doesn't poison the rest of the suite.
const origExists = fs.existsSync;
const origRead = fs.readFileSync;
let warnCalled = false;
try {
fs.existsSync = () => true;
fs.readFileSync = () => { throw new Error('EACCES: permission denied'); };
delete require.cache[require.resolve('../src/managers/license-manager')];
const { LicenseManager } = require('../src/managers/license-manager');
const mgr = new LicenseManager({}, '/nonexistent', { info: () => {}, warn: () => { warnCalled = true; } });
const result = mgr.loadSecret('/fake/secret/path');
expect(result).toBe(false);
expect(mgr.masterSecretHash).toBeNull();
expect(warnCalled).toBe(true);
} finally {
fs.existsSync = origExists;
fs.readFileSync = origRead;
_cleanup(dir);
}
});
});
// ===========================================================================
// _validateOffline()
// ===========================================================================
describe('LicenseManager: _validateOffline()', () => {
test('validates real code with loaded secret', () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const code = _mintCode(TEST_SECRET, 30, 1600);
const result = mgr._validateOffline(code);
expect(result.valid).toBe(true);
expect(result.durationDays).toBe(30);
expect(result.codeId).toBe(1600);
} finally { restore(); }
});
test('rejects forged code (HMAC mismatch)', () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const wrongSecret = crypto.randomBytes(32).toString('hex');
const forgedCode = _mintCode(wrongSecret, 30, 1700);
const result = mgr._validateOffline(forgedCode);
expect(result.valid).toBe(false);
expect(result.reason).toMatch(/signature|forged|corrupted/i);
} finally { restore(); }
});
test('returns validation-unavailable when no secret loaded', () => {
const { mgr, restore } = _makeManager({ secret: null });
try {
const code = _mintCode(TEST_SECRET, 30, 1800);
const result = mgr._validateOffline(code);
expect(result.valid).toBe(false);
expect(result.reason).toMatch(/unavailable|internet/i);
} finally { restore(); }
});
test('returns invalid for malformed code string', () => {
const { mgr, restore } = _makeManager({ secret: null });
try {
const result = mgr._validateOffline('DC-GARBAGE');
expect(result.valid).toBe(false);
} finally { restore(); }
});
test('rejects code with unsupported version (forged version-2 payload)', () => {
// Construct a code whose version nibble is 2 (not the current VERSION=1).
// We can't use generateCode() because it hardcodes VERSION=1, so we
// manually pack a version=2 payload, sign it with the correct HMAC, and
// base32-encode it into the DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX format.
const { generateCode, parseCode } = require('../license-keygen');
// Generate a valid version-1 code, decode it, bump the version nibble,
// re-sign, re-encode. This gives a cryptographically well-formed code
// with version=2 — the structural check should catch it.
const realCode = generateCode(TEST_SECRET, 30, 1900);
// Decode payload + signature from the real code
const cleaned = realCode.replace(/^DC-/, '').replace(/-/g, '');
const BASE32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
function b32Decode(str) {
let bits = '';
for (const ch of str.toUpperCase()) { bits += BASE32.indexOf(ch).toString(2).padStart(5, '0'); }
const bytes = [];
for (let i = 0; i + 8 <= bits.length; i += 8) bytes.push(parseInt(bits.substring(i, i + 8), 2));
return Buffer.from(bytes);
}
const decoded = b32Decode(cleaned);
const payload = Buffer.from(decoded.subarray(0, 10));
// Overwrite version nibble: bits 12-15 of the first 16-bit value.
const versionAndDuration = payload.readUInt16BE(0);
const duration = versionAndDuration & 0x0FFF;
payload.writeUInt16BE((2 << 12) | duration, 0); // version=2
// Re-sign with the same secret so HMAC is valid for the tampered payload
const hmac = crypto.createHmac('sha256', TEST_SECRET).update(payload).digest();
const signature = hmac.subarray(0, 5);
const combined = Buffer.concat([payload, signature]);
// Re-encode base32
function b32Encode(buf) {
let bits = '';
for (const b of buf) bits += b.toString(2).padStart(8, '0');
while (bits.length % 5 !== 0) bits += '0';
let result = '';
for (let i = 0; i < bits.length; i += 5) result += BASE32[parseInt(bits.substring(i, i + 5), 2)];
return result;
}
let encoded = b32Encode(combined);
while (encoded.length < 25) encoded += '0';
encoded = encoded.substring(0, 25);
const groups = [];
for (let i = 0; i < 25; i += 5) groups.push(encoded.substring(i, i + 5));
const forgedV2Code = `DC-${groups.join('-')}`;
// Verify the forged code actually parses as version=2
const parsed = parseCode(forgedV2Code);
expect(parsed.version).toBe(2);
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
const result = mgr._validateOffline(forgedV2Code);
expect(result.valid).toBe(false);
expect(result.reason).toMatch(/version/i);
} finally { restore(); }
});
});
// ===========================================================================
// _maskCode()
// ===========================================================================
describe('LicenseManager: _maskCode()', () => {
test('masks a standard code (DC + 5 groups)', () => {
const { mgr, restore } = _makeManager();
try {
const masked = mgr._maskCode('DC-ABCDE-FGHIJ-KLMNO-PQRST-UVWXY');
expect(masked).toBe('DC-ABCDE-*****-*****-UVWXY');
} finally { restore(); }
});
test('returns DC-***** for short code (< 4 groups)', () => {
const { mgr, restore } = _makeManager();
try {
expect(mgr._maskCode('DC-ABC')).toBe('DC-*****');
expect(mgr._maskCode('DC-ABC-DEF')).toBe('DC-*****');
} finally { restore(); }
});
test('returns "none" for null/empty', () => {
const { mgr, restore } = _makeManager();
try {
expect(mgr._maskCode(null)).toBe('none');
expect(mgr._maskCode('')).toBe('none');
expect(mgr._maskCode(undefined)).toBe('none');
} finally { restore(); }
});
});
// ===========================================================================
// allowsLifetimeLicense()
// ===========================================================================
describe('LicenseManager: allowsLifetimeLicense()', () => {
test('defaults to false', () => {
const { mgr, restore } = _makeManager();
try {
expect(mgr.allowsLifetimeLicense()).toBe(false);
} finally { restore(); }
});
test('returns true when ALLOW_LIFETIME_LICENSE=true', () => {
const { mgr, restore } = _makeManager({
env: { ALLOW_LIFETIME_LICENSE: 'true' },
});
try {
expect(mgr.allowsLifetimeLicense()).toBe(true);
} finally { restore(); }
});
test('returns false for non-"true" values', () => {
const { mgr, restore } = _makeManager({
env: { ALLOW_LIFETIME_LICENSE: 'false' },
});
try {
expect(mgr.allowsLifetimeLicense()).toBe(false);
} finally { restore(); }
});
});
// ===========================================================================
// _updateConfig() — internal but critical for persistence
// ===========================================================================
describe('LicenseManager: _updateConfig()', () => {
test('creates config.json if it does not exist', async () => {
const { mgr, restore, configFile } = _makeManager();
try {
expect(fs.existsSync(configFile)).toBe(false);
mgr.activation = {
code: 'DC-CREATE-CONFIG',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
await mgr._updateConfig();
expect(fs.existsSync(configFile)).toBe(true);
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
expect(config.license.active).toBe(true);
expect(config.licenseBackup.code).toBe('DC-CREATE-CONFIG');
} finally { restore(); }
});
test('preserves existing config fields when updating', async () => {
const { mgr, restore, configFile } = _makeManager();
try {
fs.writeFileSync(configFile, JSON.stringify({
tld: '.sami',
existingField: 'preserved',
}, null, 2));
mgr.activation = {
code: 'DC-PRESERVE-CONFIG',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
validationMethod: 'offline',
features: ['sso'],
};
await mgr._updateConfig();
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
expect(config.tld).toBe('.sami');
expect(config.existingField).toBe('preserved');
expect(config.license.active).toBe(true);
} finally { restore(); }
});
test('clears licenseBackup when activation is null/expired', async () => {
const { mgr, restore, configFile } = _makeManager();
try {
fs.writeFileSync(configFile, JSON.stringify({
license: { active: true, tier: 'premium' },
licenseBackup: { code: 'DC-OLD', durationDays: 30 },
}, null, 2));
mgr.activation = null;
await mgr._updateConfig();
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
expect(config.license.active).toBe(false);
expect(config.license.tier).toBe('free');
expect(config.licenseBackup).toBeUndefined();
} finally { restore(); }
});
test('does not crash when config file directory does not exist', async () => {
const { mgr, restore } = _makeManager();
try {
// Point to a path inside a nonexistent directory
mgr.configFile = '/nonexistent/dir/config.json';
mgr.activation = {
code: 'DC-NO-CRASH',
durationDays: 30,
lifetime: false,
activatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
machineId: 'test',
features: ['sso'],
};
// Should not throw — _updateConfig catches internally
await expect(mgr._updateConfig()).resolves.not.toThrow();
} finally { restore(); }
});
});
// ===========================================================================
// Full lifecycle integration
// ===========================================================================
describe('LicenseManager: full lifecycle integration', () => {
test('activate → getStatus → deactivate → getStatus', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
// Initially free
expect(mgr.getStatus().tier).toBe('free');
// Activate
const code = _mintCode(TEST_SECRET, 90, 2000);
const activateResult = await mgr.activate(code);
expect(activateResult.success).toBe(true);
// Check status is premium
const activeStatus = mgr.getStatus();
expect(activeStatus.active).toBe(true);
expect(activeStatus.tier).toBe('premium');
expect(activeStatus.durationDays).toBe(90);
// Deactivate
const deactivateResult = await mgr.deactivate();
expect(deactivateResult.success).toBe(true);
// Back to free
expect(mgr.getStatus().tier).toBe('free');
expect(mgr.isPro()).toBe(false);
} finally { await restore(); }
});
test('load() after activate() restores the same activation', async () => {
const creds = {
_store: {},
async store(key, val) { this._store[key] = val; },
async retrieve(key) { return this._store[key] || null; },
async delete(key) { delete this._store[key]; },
};
const dir = _tmpDir();
try {
const configFile = path.join(dir, 'config.json');
const secretFile = path.join(dir, '.license-secret');
fs.writeFileSync(secretFile, TEST_SECRET, 'utf8');
delete require.cache[require.resolve('../src/managers/license-manager')];
const { LicenseManager } = require('../src/managers/license-manager');
// Activate
const mgr1 = new LicenseManager(creds, configFile, { info: () => {} });
mgr1.loadSecret(secretFile);
const code = _mintCode(TEST_SECRET, 30, 2100);
await mgr1.activate(code);
const originalCodeId = mgr1.activation.codeId;
// Simulate restart: create a NEW manager with same creds + config
const mgr2 = new LicenseManager(creds, configFile, { info: () => {} });
await mgr2.load();
expect(mgr2.activation).toBeTruthy();
expect(mgr2.activation.code).toBe(code);
expect(mgr2.activation.codeId).toBe(originalCodeId);
expect(mgr2.isPro()).toBe(true);
} finally { _cleanup(dir); }
});
test('freshly minted code validates as active (not expired)', async () => {
const { mgr, restore } = _makeManager({ secret: TEST_SECRET });
try {
// Generate a code and verify it independently
const code = _mintCode(TEST_SECRET, 30, 2200);
const verifyResult = verifyCode(TEST_SECRET, code);
expect(verifyResult.valid).toBe(true);
expect(verifyResult.durationDays).toBe(30);
expect(verifyResult.expired).toBe(false);
// Activate should succeed (code is cryptographically valid + not expired)
const result = await mgr.activate(code);
expect(result.success).toBe(true);
expect(result.activation.expired).toBe(false);
} finally { await restore(); }
});
});