UNPKG

@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
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(); }); }); });