feat(browser-utils): add automatic OS version detection
Implements automatic detection of OS versions at runtime instead of hardcoded values. This ensures the library reports accurate platform versions to WhatsApp without manual updates. Version Detection: - Linux: Reads /etc/os-release for distribution version (e.g., '24.04.1') - macOS: Converts Darwin kernel version to macOS version (e.g., Darwin 24.x → macOS 15.x) - Windows: Uses os.release() directly (already returns correct format) Security Fixes (from PR #62 review): - Fix isValidBrowserPreset() to use Object.prototype.hasOwnProperty.call() to prevent matching inherited properties like 'toString' or 'constructor' - Fix getPlatformId() signature to accept 'unknown' type for runtime safety Other Improvements: - Add FALLBACK_VERSIONS constant with updated 2025 versions - Add DARWIN_TO_MACOS mapping for accurate macOS version conversion - Export detectedOSVersions for debugging and logging - Improve JSDoc documentation with accurate examples - Add 36 comprehensive unit tests (up from 25) The version is detected once at module load and cached for consistent behavior throughout the application lifecycle. https://claude.ai/code/session_018XbFWEYwCfeeXioFDLvSXV
This commit is contained in:
@@ -1,30 +1,44 @@
|
||||
import { Browsers, getPlatformId, isValidBrowserPreset } from '../../Utils/browser-utils'
|
||||
import { Browsers, getPlatformId, isValidBrowserPreset, detectedOSVersions } from '../../Utils/browser-utils'
|
||||
|
||||
describe('browser-utils', () => {
|
||||
describe('Browsers', () => {
|
||||
describe('ubuntu', () => {
|
||||
it('should return correct tuple for Ubuntu', () => {
|
||||
const result = Browsers.ubuntu('Chrome')
|
||||
expect(result).toEqual(['Ubuntu', 'Chrome', '22.04.4'])
|
||||
expect(result[0]).toBe('Ubuntu')
|
||||
expect(result[1]).toBe('Chrome')
|
||||
expect(result[2]).toMatch(/^\d+\.\d+/) // Version format like '24.04' or '24.04.1'
|
||||
})
|
||||
|
||||
it('should handle empty browser string', () => {
|
||||
const result = Browsers.ubuntu('')
|
||||
expect(result).toEqual(['Ubuntu', '', '22.04.4'])
|
||||
expect(result[0]).toBe('Ubuntu')
|
||||
expect(result[1]).toBe('')
|
||||
})
|
||||
|
||||
it('should use auto-detected or fallback version', () => {
|
||||
const result = Browsers.ubuntu('Test')
|
||||
// Version should be a non-empty string
|
||||
expect(typeof result[2]).toBe('string')
|
||||
expect(result[2].length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('macOS', () => {
|
||||
it('should return correct tuple for macOS', () => {
|
||||
const result = Browsers.macOS('Safari')
|
||||
expect(result).toEqual(['Mac OS', 'Safari', '14.4.1'])
|
||||
expect(result[0]).toBe('Mac OS')
|
||||
expect(result[1]).toBe('Safari')
|
||||
expect(result[2]).toMatch(/^\d+\.\d+/) // Version format like '15.2'
|
||||
})
|
||||
})
|
||||
|
||||
describe('windows', () => {
|
||||
it('should return correct tuple for Windows', () => {
|
||||
const result = Browsers.windows('Edge')
|
||||
expect(result).toEqual(['Windows', 'Edge', '10.0.22631'])
|
||||
expect(result[0]).toBe('Windows')
|
||||
expect(result[1]).toBe('Edge')
|
||||
expect(result[2]).toMatch(/^\d+\.\d+\.\d+/) // Version format like '10.0.26100'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -51,6 +65,41 @@ describe('browser-utils', () => {
|
||||
// Should be a valid platform name
|
||||
expect(['Ubuntu', 'Mac OS', 'Windows', 'AIX', 'Android', 'FreeBSD', 'OpenBSD', 'Solaris']).toContain(result[0])
|
||||
})
|
||||
|
||||
it('should return a valid version string', () => {
|
||||
const result = Browsers.appropriate('Test')
|
||||
expect(result[2]).toBeDefined()
|
||||
expect(typeof result[2]).toBe('string')
|
||||
expect(result[2].length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectedOSVersions', () => {
|
||||
it('should expose detected versions', () => {
|
||||
expect(detectedOSVersions).toBeDefined()
|
||||
expect(typeof detectedOSVersions.ubuntu).toBe('string')
|
||||
expect(typeof detectedOSVersions.macOS).toBe('string')
|
||||
expect(typeof detectedOSVersions.windows).toBe('string')
|
||||
expect(typeof detectedOSVersions.baileys).toBe('string')
|
||||
})
|
||||
|
||||
it('should have valid version formats', () => {
|
||||
// Ubuntu: X.X or X.X.X
|
||||
expect(detectedOSVersions.ubuntu).toMatch(/^\d+\.\d+(\.\d+)?$/)
|
||||
// macOS: X.X or X.X.X
|
||||
expect(detectedOSVersions.macOS).toMatch(/^\d+\.\d+(\.\d+)?$/)
|
||||
// Windows: X.X.XXXXX
|
||||
expect(detectedOSVersions.windows).toMatch(/^\d+\.\d+\.\d+$/)
|
||||
// Baileys: semver
|
||||
expect(detectedOSVersions.baileys).toMatch(/^\d+\.\d+\.\d+$/)
|
||||
})
|
||||
|
||||
it('should match Browsers preset versions', () => {
|
||||
expect(Browsers.ubuntu('Test')[2]).toBe(detectedOSVersions.ubuntu)
|
||||
expect(Browsers.macOS('Test')[2]).toBe(detectedOSVersions.macOS)
|
||||
expect(Browsers.windows('Test')[2]).toBe(detectedOSVersions.windows)
|
||||
expect(Browsers.baileys('Test')[2]).toBe(detectedOSVersions.baileys)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -89,34 +138,37 @@ describe('browser-utils', () => {
|
||||
})
|
||||
|
||||
it('should return default ID for undefined input', () => {
|
||||
// @ts-expect-error - Testing runtime behavior with invalid input
|
||||
const result = getPlatformId(undefined)
|
||||
expect(result).toBe('1')
|
||||
})
|
||||
|
||||
it('should return default ID for null input', () => {
|
||||
// @ts-expect-error - Testing runtime behavior with invalid input
|
||||
const result = getPlatformId(null)
|
||||
expect(result).toBe('1')
|
||||
})
|
||||
|
||||
it('should return default ID for number input', () => {
|
||||
// @ts-expect-error - Testing runtime behavior with invalid input
|
||||
const result = getPlatformId(123)
|
||||
expect(result).toBe('1')
|
||||
})
|
||||
|
||||
it('should return default ID for object input', () => {
|
||||
// @ts-expect-error - Testing runtime behavior with invalid input
|
||||
const result = getPlatformId({ browser: 'chrome' })
|
||||
expect(result).toBe('1')
|
||||
})
|
||||
|
||||
it('should return default ID for array input', () => {
|
||||
const result = getPlatformId(['chrome'])
|
||||
expect(result).toBe('1')
|
||||
})
|
||||
|
||||
it('should always return a string', () => {
|
||||
const validResult = getPlatformId('chrome')
|
||||
const invalidResult = getPlatformId('nonexistent')
|
||||
const undefinedResult = getPlatformId(undefined)
|
||||
expect(typeof validResult).toBe('string')
|
||||
expect(typeof invalidResult).toBe('string')
|
||||
expect(typeof undefinedResult).toBe('string')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -133,6 +185,7 @@ describe('browser-utils', () => {
|
||||
expect(isValidBrowserPreset('invalid')).toBe(false)
|
||||
expect(isValidBrowserPreset('UBUNTU')).toBe(false)
|
||||
expect(isValidBrowserPreset('Ubuntu')).toBe(false)
|
||||
expect(isValidBrowserPreset('MacOS')).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for non-string values', () => {
|
||||
@@ -141,6 +194,17 @@ describe('browser-utils', () => {
|
||||
expect(isValidBrowserPreset(123)).toBe(false)
|
||||
expect(isValidBrowserPreset({})).toBe(false)
|
||||
expect(isValidBrowserPreset([])).toBe(false)
|
||||
expect(isValidBrowserPreset(true)).toBe(false)
|
||||
})
|
||||
|
||||
// Critical security test: inherited properties should not match
|
||||
it('should return false for inherited Object properties (security)', () => {
|
||||
expect(isValidBrowserPreset('toString')).toBe(false)
|
||||
expect(isValidBrowserPreset('constructor')).toBe(false)
|
||||
expect(isValidBrowserPreset('valueOf')).toBe(false)
|
||||
expect(isValidBrowserPreset('hasOwnProperty')).toBe(false)
|
||||
expect(isValidBrowserPreset('__proto__')).toBe(false)
|
||||
expect(isValidBrowserPreset('prototype')).toBe(false)
|
||||
})
|
||||
|
||||
it('should work as type guard', () => {
|
||||
@@ -165,12 +229,11 @@ describe('browser-utils', () => {
|
||||
it('should not throw on getPlatformId with any input', () => {
|
||||
expect(() => getPlatformId('valid')).not.toThrow()
|
||||
expect(() => getPlatformId('')).not.toThrow()
|
||||
// @ts-expect-error - Testing runtime behavior
|
||||
expect(() => getPlatformId(undefined)).not.toThrow()
|
||||
// @ts-expect-error - Testing runtime behavior
|
||||
expect(() => getPlatformId(null)).not.toThrow()
|
||||
// @ts-expect-error - Testing runtime behavior
|
||||
expect(() => getPlatformId({})).not.toThrow()
|
||||
expect(() => getPlatformId([])).not.toThrow()
|
||||
expect(() => getPlatformId(Symbol('test'))).not.toThrow()
|
||||
})
|
||||
|
||||
it('all Browsers methods should return readonly-compatible tuples', () => {
|
||||
@@ -182,5 +245,35 @@ describe('browser-utils', () => {
|
||||
expect(result.every(item => typeof item === 'string')).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('version strings should never be empty', () => {
|
||||
const presets = ['ubuntu', 'macOS', 'windows', 'baileys', 'appropriate'] as const
|
||||
for (const preset of presets) {
|
||||
const result = Browsers[preset]('Test')
|
||||
expect(result[2].length).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('version detection', () => {
|
||||
it('should detect a reasonable Ubuntu version', () => {
|
||||
const version = detectedOSVersions.ubuntu
|
||||
// Should be at least 18.04 (oldest supported LTS)
|
||||
const majorVersion = parseFloat(version)
|
||||
expect(majorVersion).toBeGreaterThanOrEqual(18)
|
||||
})
|
||||
|
||||
it('should detect a reasonable macOS version', () => {
|
||||
const version = detectedOSVersions.macOS
|
||||
// Should be at least macOS 10.x or 11+
|
||||
const majorVersion = parseFloat(version)
|
||||
expect(majorVersion).toBeGreaterThanOrEqual(10)
|
||||
})
|
||||
|
||||
it('should detect a reasonable Windows version', () => {
|
||||
const version = detectedOSVersions.windows
|
||||
// Should start with 10. (Windows 10/11)
|
||||
expect(version).toMatch(/^10\./)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user