@ufdevsllc/authme2.0
Version:
SDK for license management and remote monitoring with automatic system tracking, license validation, and remote control capabilities
565 lines (460 loc) • 22 kB
JavaScript
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from 'vitest';
import LicenseValidator from '../license-validator.js';
import DatabaseManager from '../database-manager.js';
// Mock the DatabaseManager and other modules
vi.mock('../database-manager.js');
vi.mock('os');
vi.mock('crypto');
describe('LicenseValidator', () => {
let licenseValidator;
let mockDbManager;
let mockDb;
let mockLicenseModel;
let mockUsageLogModel;
beforeAll(async () => {
// Mock os module
const os = await import('os');
vi.mocked(os.hostname).mockReturnValue('test-hostname');
vi.mocked(os.platform).mockReturnValue('linux');
vi.mocked(os.arch).mockReturnValue('x64');
vi.mocked(os.networkInterfaces).mockReturnValue({
eth0: [{ mac: '00:11:22:33:44:55', internal: false }]
});
vi.mocked(os.totalmem).mockReturnValue(8589934592);
vi.mocked(os.freemem).mockReturnValue(4294967296);
vi.mocked(os.cpus).mockReturnValue([{}, {}, {}, {}]);
vi.mocked(os.uptime).mockReturnValue(3600);
// Mock crypto module
const crypto = await import('crypto');
const mockHash = {
update: vi.fn().mockReturnThis(),
digest: vi.fn().mockReturnValue('abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890')
};
vi.mocked(crypto.createHash).mockReturnValue(mockHash);
});
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks();
// Create mock database models with chainable methods
mockLicenseModel = {
findOne: vi.fn().mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(null)
})),
create: vi.fn(),
save: vi.fn()
};
mockUsageLogModel = {
findOne: vi.fn().mockImplementation(() => ({
sort: vi.fn().mockResolvedValue(null)
})),
create: vi.fn(),
find: vi.fn()
};
// Create mock database connection
mockDb = {
model: vi.fn((name) => {
if (name === 'License') return mockLicenseModel;
if (name === 'UsageLog') return mockUsageLogModel;
return null;
})
};
// Create mock database manager
mockDbManager = {
initMonitoringConnection: vi.fn().mockResolvedValue(),
isMonitoringConnected: vi.fn().mockReturnValue(true),
getMonitoringDB: vi.fn().mockReturnValue(mockDb),
closeConnection: vi.fn().mockResolvedValue()
};
// Mock the DatabaseManager constructor
vi.mocked(DatabaseManager).mockImplementation(() => mockDbManager);
// Create license validator instance
licenseValidator = new LicenseValidator();
});
afterEach(async () => {
if (licenseValidator) {
await licenseValidator.cleanup();
}
});
describe('constructor', () => {
it('should initialize with correct properties', () => {
expect(licenseValidator.systemId).toBe('abcdef1234567890abcdef1234567890');
expect(licenseValidator.validationCache).toBeInstanceOf(Map);
expect(licenseValidator.cacheTimeout).toBe(5 * 60 * 1000);
});
});
describe('generateSystemId', () => {
it('should generate a consistent system ID', () => {
const systemId1 = licenseValidator.generateSystemId();
const systemId2 = licenseValidator.generateSystemId();
expect(systemId1).toBe(systemId2);
expect(systemId1).toHaveLength(32);
expect(systemId1).toBe('abcdef1234567890abcdef1234567890');
});
});
describe('initialize', () => {
it('should initialize database connection successfully', async () => {
await licenseValidator.initialize();
expect(mockDbManager.initMonitoringConnection).toHaveBeenCalledOnce();
});
it('should throw error if database initialization fails', async () => {
mockDbManager.initMonitoringConnection.mockRejectedValue(new Error('Connection failed'));
await expect(licenseValidator.initialize()).rejects.toThrow('License validator initialization failed: Connection failed');
});
});
describe('validateLicense', () => {
const validLicense = {
licenseKey: 'test-license-key-123',
distributionLimit: 10,
currentDistributions: 5,
isActive: true,
expiresAt: new Date(Date.now() + 86400000), // 1 day from now
vendorId: 'test-vendor',
productId: 'test-product'
};
beforeEach(() => {
mockUsageLogModel.create.mockResolvedValue({});
});
it('should return invalid result for invalid license key', async () => {
const result1 = await licenseValidator.validateLicense(null);
expect(result1.isValid).toBe(false);
expect(result1.reason).toBe('INVALID_LICENSE_KEY');
expect(result1.canStartApplication).toBe(false);
const result2 = await licenseValidator.validateLicense(123);
expect(result2.isValid).toBe(false);
expect(result2.reason).toBe('INVALID_LICENSE_KEY');
expect(result2.canStartApplication).toBe(false);
});
it('should return invalid result for non-existent license', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(null)
}));
const result = await licenseValidator.validateLicense('non-existent-key');
expect(result.isValid).toBe(false);
expect(result.reason).toBe('LICENSE_NOT_FOUND');
expect(result.canStartApplication).toBe(false);
expect(mockUsageLogModel.create).toHaveBeenCalledWith(
expect.objectContaining({
licenseKey: 'non-existent-key',
action: 'validation',
success: false
})
);
});
it('should return invalid result for inactive license', async () => {
const inactiveLicense = { ...validLicense, isActive: false };
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(inactiveLicense)
}));
const result = await licenseValidator.validateLicense('test-key');
expect(result.isValid).toBe(false);
expect(result.reason).toBe('LICENSE_INACTIVE');
expect(result.canStartApplication).toBe(false);
expect(result.license).toBeDefined();
});
it('should return invalid result for expired license', async () => {
const expiredLicense = {
...validLicense,
expiresAt: new Date(Date.now() - 86400000) // 1 day ago
};
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(expiredLicense)
}));
const result = await licenseValidator.validateLicense('test-key');
expect(result.isValid).toBe(false);
expect(result.reason).toBe('LICENSE_EXPIRED');
expect(result.canStartApplication).toBe(false);
});
it('should return invalid result for distribution limit exceeded', async () => {
const limitExceededLicense = {
...validLicense,
currentDistributions: 10,
distributionLimit: 10
};
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(limitExceededLicense)
}));
const result = await licenseValidator.validateLicense('test-key');
expect(result.isValid).toBe(false);
expect(result.reason).toBe('DISTRIBUTION_LIMIT_EXCEEDED');
expect(result.canStartApplication).toBe(false);
expect(result.distributionInfo).toBeDefined();
});
it('should return valid result for valid license', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(validLicense)
}));
const result = await licenseValidator.validateLicense('test-key');
expect(result.isValid).toBe(true);
expect(result.reason).toBe('LICENSE_VALID');
expect(result.canStartApplication).toBe(true);
expect(result.license).toBeDefined();
expect(result.distributionInfo).toBeDefined();
});
it('should use cache for subsequent calls', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(validLicense)
}));
// First call
const result1 = await licenseValidator.validateLicense('test-key');
expect(mockLicenseModel.findOne).toHaveBeenCalledOnce();
// Second call should use cache
const result2 = await licenseValidator.validateLicense('test-key');
expect(mockLicenseModel.findOne).toHaveBeenCalledOnce(); // Still only once
expect(result1).toEqual(result2);
});
it('should bypass cache when useCache is false', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(validLicense)
}));
// First call
await licenseValidator.validateLicense('test-key');
expect(mockLicenseModel.findOne).toHaveBeenCalledOnce();
// Second call with cache disabled
await licenseValidator.validateLicense('test-key', false);
expect(mockLicenseModel.findOne).toHaveBeenCalledTimes(2);
});
it('should handle database errors gracefully', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockRejectedValue(new Error('Database error'))
}));
const result = await licenseValidator.validateLicense('test-key');
expect(result.isValid).toBe(false);
expect(result.reason).toBe('VALIDATION_ERROR');
expect(result.error).toBe('Database error');
});
});
describe('checkDistributionLimit', () => {
const testLicense = {
licenseKey: 'test-key',
distributionLimit: 10,
currentDistributions: 7
};
it('should return correct distribution info when license is provided', async () => {
const result = await licenseValidator.checkDistributionLimit('test-key', testLicense);
expect(result.canAcceptNewDistribution).toBe(true);
expect(result.currentDistributions).toBe(7);
expect(result.distributionLimit).toBe(10);
expect(result.remainingDistributions).toBe(3);
expect(result.utilizationPercentage).toBe(70);
});
it('should query database when license is not provided', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(testLicense)
}));
const result = await licenseValidator.checkDistributionLimit('test-key');
expect(mockLicenseModel.findOne).toHaveBeenCalledWith({ licenseKey: 'test-key' });
expect(result.canAcceptNewDistribution).toBe(true);
});
it('should return false when distribution limit is reached', async () => {
const limitReachedLicense = { ...testLicense, currentDistributions: 10 };
const result = await licenseValidator.checkDistributionLimit('test-key', limitReachedLicense);
expect(result.canAcceptNewDistribution).toBe(false);
expect(result.remainingDistributions).toBe(0);
expect(result.utilizationPercentage).toBe(100);
});
it('should throw error when license is not found', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(null)
}));
await expect(licenseValidator.checkDistributionLimit('test-key')).rejects.toThrow('Failed to check distribution limit: License not found');
});
});
describe('trackUsage', () => {
const testLicense = {
licenseKey: 'test-key',
distributionLimit: 10,
currentDistributions: 5,
save: vi.fn().mockResolvedValue()
};
beforeEach(() => {
mockUsageLogModel.create.mockResolvedValue({});
});
it('should track usage for new system', async () => {
mockLicenseModel.findOne.mockResolvedValue(testLicense);
mockUsageLogModel.findOne.mockImplementation(() => ({
sort: vi.fn().mockResolvedValue(null)
}));
const result = await licenseValidator.trackUsage('test-key', { platform: 'linux' });
expect(result.success).toBe(true);
expect(result.isNewDistribution).toBe(true);
expect(testLicense.currentDistributions).toBe(6);
expect(testLicense.save).toHaveBeenCalled();
});
it('should handle existing system without incrementing distribution', async () => {
mockLicenseModel.findOne.mockResolvedValue(testLicense);
mockUsageLogModel.findOne.mockImplementation(() => ({
sort: vi.fn().mockResolvedValue({ systemId: 'existing-system' })
}));
const result = await licenseValidator.trackUsage('test-key');
expect(result.success).toBe(true);
expect(result.isNewDistribution).toBe(false);
expect(testLicense.save).not.toHaveBeenCalled();
});
it('should throw error when distribution limit is exceeded', async () => {
const limitReachedLicense = {
...testLicense,
currentDistributions: 10,
distributionLimit: 10
};
mockLicenseModel.findOne.mockResolvedValue(limitReachedLicense);
mockUsageLogModel.findOne.mockImplementation(() => ({
sort: vi.fn().mockResolvedValue(null)
}));
await expect(licenseValidator.trackUsage('test-key')).rejects.toThrow('Failed to track usage: Distribution limit exceeded');
});
it('should throw error when license is not found', async () => {
mockLicenseModel.findOne.mockResolvedValue(null);
await expect(licenseValidator.trackUsage('test-key')).rejects.toThrow('Failed to track usage: License not found');
});
});
describe('isLicenseActive', () => {
it('should return true for valid license', async () => {
const validLicense = {
licenseKey: 'test-key',
distributionLimit: 10,
currentDistributions: 5,
isActive: true,
expiresAt: new Date(Date.now() + 86400000)
};
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(validLicense)
}));
mockUsageLogModel.create.mockResolvedValue({});
const result = await licenseValidator.isLicenseActive('test-key');
expect(result).toBe(true);
});
it('should return false for invalid license', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(null)
}));
mockUsageLogModel.create.mockResolvedValue({});
const result = await licenseValidator.isLicenseActive('test-key');
expect(result).toBe(false);
});
it('should return false on validation error', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockRejectedValue(new Error('Database error'))
}));
const result = await licenseValidator.isLicenseActive('test-key');
expect(result).toBe(false);
});
});
describe('preventStartupOnInvalidLicense', () => {
const validLicense = {
licenseKey: 'test-key',
distributionLimit: 10,
currentDistributions: 5,
isActive: true,
expiresAt: new Date(Date.now() + 86400000)
};
beforeEach(() => {
mockUsageLogModel.create.mockResolvedValue({});
mockUsageLogModel.findOne.mockImplementation(() => ({
sort: vi.fn().mockResolvedValue(null)
}));
});
it('should not throw for valid license', async () => {
const validLicenseWithRoom = { ...validLicense, currentDistributions: 3 }; // Leave room for new distribution
// Mock for validateLicense call
mockLicenseModel.findOne.mockImplementationOnce(() => ({
lean: vi.fn().mockResolvedValue(validLicenseWithRoom)
}));
// Mock for trackUsage call - return a license object with save method
mockLicenseModel.findOne.mockResolvedValueOnce({
...validLicenseWithRoom,
save: vi.fn().mockResolvedValue()
});
await expect(licenseValidator.preventStartupOnInvalidLicense('test-key', { exitOnFailure: false }))
.resolves.not.toThrow();
});
it('should throw error for invalid license when exitOnFailure is false', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(null)
}));
await expect(licenseValidator.preventStartupOnInvalidLicense('test-key', { exitOnFailure: false }))
.rejects.toThrow('APPLICATION STARTUP BLOCKED');
});
it('should call custom error handler when provided', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockResolvedValue(null)
}));
const customErrorHandler = vi.fn();
await licenseValidator.preventStartupOnInvalidLicense('test-key', {
exitOnFailure: false,
customErrorHandler
});
expect(customErrorHandler).toHaveBeenCalledWith(
expect.objectContaining({
isValid: false,
reason: 'LICENSE_NOT_FOUND'
})
);
});
it('should handle validation errors', async () => {
mockLicenseModel.findOne.mockImplementation(() => ({
lean: vi.fn().mockRejectedValue(new Error('Database error'))
}));
await expect(licenseValidator.preventStartupOnInvalidLicense('test-key', { exitOnFailure: false }))
.rejects.toThrow('LICENSE VALIDATION ERROR');
});
});
describe('sanitizeLicenseData', () => {
it('should sanitize license data correctly', () => {
const license = {
licenseKey: 'very-long-license-key-123456789',
distributionLimit: 10,
currentDistributions: 5,
isActive: true,
expiresAt: new Date(),
vendorId: 'test-vendor',
productId: 'test-product',
sensitiveField: 'should-not-appear'
};
const sanitized = licenseValidator.sanitizeLicenseData(license);
expect(sanitized.licenseKey).toBe('very-lon...');
expect(sanitized.distributionLimit).toBe(10);
expect(sanitized.currentDistributions).toBe(5);
expect(sanitized.isActive).toBe(true);
expect(sanitized.expiresAt).toBe(license.expiresAt);
expect(sanitized.vendorId).toBe('test-vendor');
expect(sanitized.productId).toBe('test-product');
expect(sanitized.sensitiveField).toBeUndefined();
});
});
describe('getSystemInfo', () => {
it('should return system information', () => {
const systemInfo = licenseValidator.getSystemInfo();
expect(systemInfo).toEqual({
systemId: 'abcdef1234567890abcdef1234567890',
hostname: 'test-hostname',
platform: 'linux',
arch: 'x64',
nodeVersion: process.version,
totalMemory: 8589934592,
freeMemory: 4294967296,
cpus: 4,
uptime: 3600
});
});
});
describe('clearCache', () => {
it('should clear the validation cache', () => {
licenseValidator.validationCache.set('test-key', { result: {}, timestamp: Date.now() });
expect(licenseValidator.validationCache.size).toBe(1);
licenseValidator.clearCache();
expect(licenseValidator.validationCache.size).toBe(0);
});
});
describe('cleanup', () => {
it('should cleanup resources', async () => {
licenseValidator.validationCache.set('test-key', { result: {}, timestamp: Date.now() });
await licenseValidator.cleanup();
expect(licenseValidator.validationCache.size).toBe(0);
expect(mockDbManager.closeConnection).toHaveBeenCalled();
});
it('should handle cleanup errors gracefully', async () => {
mockDbManager.closeConnection.mockRejectedValue(new Error('Cleanup error'));
await expect(licenseValidator.cleanup()).resolves.not.toThrow();
});
});
});