[grade=A] Harden server-managed license renewals
This commit is contained in:
@@ -146,7 +146,7 @@ describe('LicenseManager: load()', () => {
|
||||
} finally { await restore(); }
|
||||
});
|
||||
|
||||
test('logs expired license on load but keeps it', async () => {
|
||||
test('fails closed and removes expired license on load', async () => {
|
||||
const { mgr, restore } = _makeManager();
|
||||
try {
|
||||
const activation = {
|
||||
@@ -162,7 +162,7 @@ describe('LicenseManager: load()', () => {
|
||||
await mgr.credentialManager.store('license.activation', JSON.stringify(activation));
|
||||
|
||||
await mgr.load();
|
||||
expect(mgr.activation).toBeTruthy();
|
||||
expect(mgr.activation).toBeNull();
|
||||
expect(mgr.isExpired()).toBe(true);
|
||||
expect(mgr._loaded).toBe(true);
|
||||
} finally { await restore(); }
|
||||
@@ -512,7 +512,7 @@ describe('LicenseManager: activate() — online validation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('falls back to offline when server is unreachable (fetch throws)', async () => {
|
||||
test('does not mint a new server-managed activation offline when server is unreachable', async () => {
|
||||
const originalFetch = global.fetch;
|
||||
const dir = _tmpDir();
|
||||
const prevUrl = process.env.LICENSE_SERVER_URL;
|
||||
@@ -540,8 +540,8 @@ describe('LicenseManager: activate() — online validation', () => {
|
||||
});
|
||||
|
||||
const res = await result;
|
||||
expect(res.success).toBe(true);
|
||||
expect(res.activation.validationMethod).toBe('offline');
|
||||
expect(res.success).toBe(false);
|
||||
expect(res.message).toMatch(/temporarily unavailable/);
|
||||
} finally {
|
||||
if (prevUrl === undefined) delete process.env.LICENSE_SERVER_URL;
|
||||
else process.env.LICENSE_SERVER_URL = prevUrl;
|
||||
@@ -915,19 +915,19 @@ describe('LicenseManager: isExpired()', () => {
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('false for lifetime flag only (no durationDays)', () => {
|
||||
test('fails closed for lifetime flag without signed zero duration', () => {
|
||||
const { mgr, restore } = _makeManager();
|
||||
try {
|
||||
mgr.activation = { lifetime: true, expiresAt: '2020-01-01T00:00:00Z' };
|
||||
expect(mgr.isExpired()).toBe(false);
|
||||
expect(mgr.isExpired()).toBe(true);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
test('false when expiresAt is null/missing (treated as lifetime)', () => {
|
||||
test('fails closed when expiresAt is null or missing', () => {
|
||||
const { mgr, restore } = _makeManager();
|
||||
try {
|
||||
mgr.activation = { durationDays: 30, lifetime: false, expiresAt: null };
|
||||
expect(mgr.isExpired()).toBe(false);
|
||||
expect(mgr.isExpired()).toBe(true);
|
||||
} finally { restore(); }
|
||||
});
|
||||
|
||||
@@ -1487,4 +1487,18 @@ describe('LicenseManager: full lifecycle integration', () => {
|
||||
expect(result.activation.expired).toBe(false);
|
||||
} finally { await restore(); }
|
||||
});
|
||||
|
||||
test('expired offline code cannot mint a fresh entitlement term', async () => {
|
||||
const actualNow = Date.now();
|
||||
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(actualNow - 400 * 86400000);
|
||||
const expiredCode = generateCode(TEST_SECRET, 30, 99123);
|
||||
nowSpy.mockRestore();
|
||||
const { mgr, restore } = _makeManager({ env: { LICENSE_SERVER_URL: undefined }, secret: TEST_SECRET });
|
||||
try {
|
||||
const result = await mgr.activate(expiredCode);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toMatch(/expired/i);
|
||||
expect(mgr.activation).toBeNull();
|
||||
} finally { await restore(); }
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user