This commit is contained in:
263
app/scripts/installer-config.test.ts
Normal file
263
app/scripts/installer-config.test.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import {
|
||||
getDefaultInstallerConfig,
|
||||
generateNsisDefines,
|
||||
validateInstallPath,
|
||||
isSystemPath,
|
||||
getUninstallPrompt,
|
||||
getVersionFromPackage,
|
||||
} from './installer-config.js';
|
||||
|
||||
// ─── getDefaultInstallerConfig ──────────────────────────────────────────────
|
||||
|
||||
describe('getDefaultInstallerConfig', () => {
|
||||
it('returns a valid config with expected defaults', () => {
|
||||
const config = getDefaultInstallerConfig();
|
||||
expect(config.productName).toBe('Waggle');
|
||||
expect(config.version).toBe('0.1.0');
|
||||
expect(config.publisher).toBe('Waggle');
|
||||
expect(config.defaultInstallDir).toBe('C:\\Program Files\\Waggle');
|
||||
expect(config.dataDir).toBe('~/.waggle');
|
||||
expect(config.autostart).toBe(true);
|
||||
expect(config.desktopShortcut).toBe(true);
|
||||
expect(config.startMenuEntry).toBe(true);
|
||||
expect(config.launchAfterInstall).toBe(true);
|
||||
});
|
||||
|
||||
it('returns a new object each time', () => {
|
||||
const a = getDefaultInstallerConfig();
|
||||
const b = getDefaultInstallerConfig();
|
||||
expect(a).toEqual(b);
|
||||
expect(a).not.toBe(b);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── generateNsisDefines ───────────────────────────────────────────────────
|
||||
|
||||
describe('generateNsisDefines', () => {
|
||||
it('maps all config fields to NSIS defines', () => {
|
||||
const config = getDefaultInstallerConfig();
|
||||
const defines = generateNsisDefines(config);
|
||||
|
||||
expect(defines.PRODUCT_NAME).toBe('Waggle');
|
||||
expect(defines.PRODUCT_VERSION).toBe('0.1.0');
|
||||
expect(defines.PRODUCT_PUBLISHER).toBe('Waggle');
|
||||
expect(defines.DEFAULT_INSTALL_DIR).toBe('C:\\Program Files\\Waggle');
|
||||
expect(defines.DATA_DIR).toBe('~/.waggle');
|
||||
expect(defines.AUTOSTART).toBe('1');
|
||||
expect(defines.DESKTOP_SHORTCUT).toBe('1');
|
||||
expect(defines.START_MENU_ENTRY).toBe('1');
|
||||
expect(defines.LAUNCH_AFTER_INSTALL).toBe('1');
|
||||
});
|
||||
|
||||
it('sets boolean defines to "0" when disabled', () => {
|
||||
const config = getDefaultInstallerConfig();
|
||||
config.autostart = false;
|
||||
config.desktopShortcut = false;
|
||||
config.startMenuEntry = false;
|
||||
config.launchAfterInstall = false;
|
||||
|
||||
const defines = generateNsisDefines(config);
|
||||
|
||||
expect(defines.AUTOSTART).toBe('0');
|
||||
expect(defines.DESKTOP_SHORTCUT).toBe('0');
|
||||
expect(defines.START_MENU_ENTRY).toBe('0');
|
||||
expect(defines.LAUNCH_AFTER_INSTALL).toBe('0');
|
||||
});
|
||||
|
||||
it('handles custom product names and versions', () => {
|
||||
const config = getDefaultInstallerConfig();
|
||||
config.productName = 'Waggle Pro';
|
||||
config.version = '2.5.0';
|
||||
|
||||
const defines = generateNsisDefines(config);
|
||||
expect(defines.PRODUCT_NAME).toBe('Waggle Pro');
|
||||
expect(defines.PRODUCT_VERSION).toBe('2.5.0');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── validateInstallPath ────────────────────────────────────────────────────
|
||||
|
||||
describe('validateInstallPath', () => {
|
||||
it('accepts a standard Program Files path', () => {
|
||||
const result = validateInstallPath('C:\\Program Files\\Waggle');
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('accepts a custom install path', () => {
|
||||
const result = validateInstallPath('D:\\Apps\\Waggle');
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts forward slashes', () => {
|
||||
const result = validateInstallPath('C:/Users/test/Waggle');
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts UNC paths', () => {
|
||||
const result = validateInstallPath('\\\\server\\share\\Waggle');
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects empty path', () => {
|
||||
const result = validateInstallPath('');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('empty');
|
||||
});
|
||||
|
||||
it('rejects whitespace-only path', () => {
|
||||
const result = validateInstallPath(' ');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('empty');
|
||||
});
|
||||
|
||||
it('rejects relative paths', () => {
|
||||
const result = validateInstallPath('Waggle\\bin');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('absolute');
|
||||
});
|
||||
|
||||
it('rejects paths with invalid characters', () => {
|
||||
const result = validateInstallPath('C:\\Program Files\\Waggle<test>');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('invalid characters');
|
||||
});
|
||||
|
||||
it('rejects paths that are too long', () => {
|
||||
const longPath = 'C:\\' + 'a'.repeat(250);
|
||||
const result = validateInstallPath(longPath);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('too long');
|
||||
});
|
||||
|
||||
it('rejects bare drive root', () => {
|
||||
const result = validateInstallPath('C:\\');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('drive root');
|
||||
});
|
||||
|
||||
it('rejects drive letter without backslash', () => {
|
||||
const result = validateInstallPath('C:');
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── isSystemPath ───────────────────────────────────────────────────────────
|
||||
|
||||
describe('isSystemPath', () => {
|
||||
it('detects Program Files', () => {
|
||||
expect(isSystemPath('C:\\Program Files\\Waggle')).toBe(true);
|
||||
});
|
||||
|
||||
it('detects Program Files (x86)', () => {
|
||||
expect(isSystemPath('C:\\Program Files (x86)\\Waggle')).toBe(true);
|
||||
});
|
||||
|
||||
it('detects Windows directory', () => {
|
||||
expect(isSystemPath('C:\\Windows\\System32')).toBe(true);
|
||||
});
|
||||
|
||||
it('detects ProgramData', () => {
|
||||
expect(isSystemPath('C:\\ProgramData\\Waggle')).toBe(true);
|
||||
});
|
||||
|
||||
it('is case-insensitive', () => {
|
||||
expect(isSystemPath('c:\\program files\\waggle')).toBe(true);
|
||||
expect(isSystemPath('C:\\PROGRAM FILES\\Waggle')).toBe(true);
|
||||
});
|
||||
|
||||
it('handles forward slashes', () => {
|
||||
expect(isSystemPath('C:/Program Files/Waggle')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for user directories', () => {
|
||||
expect(isSystemPath('C:\\Users\\test\\Waggle')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for custom paths', () => {
|
||||
expect(isSystemPath('D:\\Apps\\Waggle')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getUninstallPrompt ─────────────────────────────────────────────────────
|
||||
|
||||
describe('getUninstallPrompt', () => {
|
||||
it('includes the data directory in the prompt', () => {
|
||||
const prompt = getUninstallPrompt('~/.waggle');
|
||||
expect(prompt).toContain('~/.waggle');
|
||||
});
|
||||
|
||||
it('mentions keeping data for future use', () => {
|
||||
const prompt = getUninstallPrompt('C:\\Users\\test\\.waggle');
|
||||
expect(prompt).toContain('keep it for future use');
|
||||
});
|
||||
|
||||
it('mentions deleting all data option', () => {
|
||||
const prompt = getUninstallPrompt('~/.waggle');
|
||||
expect(prompt).toContain('delete all data');
|
||||
});
|
||||
|
||||
it('mentions agents and memories', () => {
|
||||
const prompt = getUninstallPrompt('~/.waggle');
|
||||
expect(prompt).toContain('agents');
|
||||
expect(prompt).toContain('memories');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getVersionFromPackage ──────────────────────────────────────────────────
|
||||
|
||||
describe('getVersionFromPackage', () => {
|
||||
const tmpDir = path.join(tmpdir(), 'waggle-installer-test-' + Date.now());
|
||||
|
||||
// Setup / teardown
|
||||
const setup = () => mkdirSync(tmpDir, { recursive: true });
|
||||
const cleanup = () => rmSync(tmpDir, { recursive: true, force: true });
|
||||
|
||||
it('reads the version from a valid package.json', () => {
|
||||
setup();
|
||||
try {
|
||||
const pkgPath = path.join(tmpDir, 'package.json');
|
||||
writeFileSync(pkgPath, JSON.stringify({ name: 'test', version: '1.2.3' }));
|
||||
expect(getVersionFromPackage(pkgPath)).toBe('1.2.3');
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws for package.json without version', () => {
|
||||
setup();
|
||||
try {
|
||||
const pkgPath = path.join(tmpDir, 'package.json');
|
||||
writeFileSync(pkgPath, JSON.stringify({ name: 'test' }));
|
||||
expect(() => getVersionFromPackage(pkgPath)).toThrow('No valid "version"');
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws for non-existent file', () => {
|
||||
expect(() => getVersionFromPackage('/nonexistent/package.json')).toThrow();
|
||||
});
|
||||
|
||||
it('reads the actual app package.json version', () => {
|
||||
const appPkgPath = path.resolve(__dirname, '..', 'package.json');
|
||||
const version = getVersionFromPackage(appPkgPath);
|
||||
expect(version).toMatch(/^\d+\.\d+\.\d+/);
|
||||
});
|
||||
|
||||
it('throws with a descriptive error for malformed JSON', () => {
|
||||
setup();
|
||||
try {
|
||||
const pkgPath = path.join(tmpDir, 'bad.json');
|
||||
writeFileSync(pkgPath, '{ not valid json!!!');
|
||||
expect(() => getVersionFromPackage(pkgPath)).toThrow('Failed to parse');
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user