/** * 9D-1/9D-2/9D-7: Tauri configuration tests. * * Validates tauri.conf.json, Cargo.toml, lib.rs, and build scripts * are properly configured for production desktop builds. */ import { describe, it, expect } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; const ROOT = path.resolve(import.meta.dirname, '..', '..', '..'); const TAURI_DIR = path.join(ROOT, 'app', 'src-tauri'); const WINDOWS_1252_EXTRA_CODEPOINTS = new Set([ 0x20ac, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x017d, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x017e, 0x0178, ]); const STAGED_DEPENDENCY_ALLOWLIST = new Set(['onnxruntime-web']); function isWindows1252PathSafe(value: string) { for (const char of value) { const code = char.codePointAt(0) ?? 0; if (code <= 0x7f || (code >= 0xa0 && code <= 0xff)) continue; if (WINDOWS_1252_EXTRA_CODEPOINTS.has(code)) continue; return false; } return true; } function listFiles(dir: string) { const files: string[] = []; const stack = [dir]; while (stack.length > 0) { const current = stack.pop()!; for (const entry of fs.readdirSync(current, { withFileTypes: true })) { const full = path.join(current, entry.name); if (entry.isDirectory()) stack.push(full); else if (entry.isFile()) files.push(full); } } return files; } function listPackageDirs(nodeModulesDir: string) { if (!fs.existsSync(nodeModulesDir)) return []; const packageDirs: string[] = []; const stack = [nodeModulesDir]; while (stack.length > 0) { const current = stack.pop()!; for (const entry of fs.readdirSync(current, { withFileTypes: true })) { if (!entry.isDirectory()) continue; const full = path.join(current, entry.name); if (fs.existsSync(path.join(full, 'package.json'))) { packageDirs.push(full); } stack.push(full); } } return packageDirs; } function resolveWithinStagedResources(fromPackageDir: string, dep: string, resourcesDir: string) { let current = fromPackageDir; for (;;) { const candidate = path.join(current, 'node_modules', ...dep.split('/'), 'package.json'); if (fs.existsSync(candidate)) return true; if (path.resolve(current) === path.resolve(resourcesDir)) return false; const parent = path.dirname(current); if (parent === current) return false; current = parent; } } describe('Tauri Production Configuration', () => { it('tauri.conf.json exists and has valid version', () => { const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); expect(conf.productName).toBe('Waggle'); expect(conf.version).toMatch(/^\d+\.\d+\.\d+$/); // Canonical bundle identifier — Egzakta-branded for code signing // + App Store / Mac notarization. Set in tauri.conf.json. Don't // change without coordinating with the signing cert subject. expect(conf.identifier).toBe('com.egzakta.waggle'); }); it('tauri.conf.json has bundle targets configured', () => { const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); // targets: "all" builds for the current platform (NSIS on Windows, DMG on macOS, etc.) expect(conf.bundle.targets).toBe('all'); expect(conf.bundle.active).toBe(true); }); it('tauri.conf.json bundles resources directory', () => { const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); expect(conf.bundle.resources).toContain('resources/*'); }); it('tauri.conf.json has correct window settings', () => { const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); const win = conf.app.windows[0]; expect(win.title).toBe('Waggle'); expect(win.width).toBe(1200); expect(win.height).toBe(800); expect(win.minWidth).toBe(800); expect(win.minHeight).toBe(600); expect(win.resizable).toBe(true); }); it('tauri.conf.json has the auto-updater intentionally disabled for v1', () => { // The updater plugin config was removed because release.yml published // latest.json with EMPTY signatures — with a pubkey present, every client // update would fail signature verification (a broken update channel). It // stays disabled until updater signing is provisioned (TAURI_SIGNING_PRIVATE_KEY // + createUpdaterArtifacts + a real-signature manifest generator). const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); expect(conf.plugins?.updater).toBeUndefined(); }); it('tauri.conf.json has tray icon configured', () => { const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); expect(conf.app.trayIcon).toBeDefined(); expect(conf.app.trayIcon.tooltip).toBe('Waggle - AI Agent Swarm'); }); it('tray menu exposes only implemented desktop actions', () => { const tray = fs.readFileSync(path.join(TAURI_DIR, 'src', 'tray.rs'), 'utf-8'); expect(tray).toContain('"Open Waggle"'); expect(tray).toContain('"Settings"'); expect(tray).toContain('"Quit Waggle"'); expect(tray).toContain('app.exit(0)'); expect(tray).toContain('"waggle://navigate"'); expect(tray).toContain('"/settings"'); expect(tray).not.toContain('"Pause Agents"'); expect(tray).not.toContain('"About Waggle"'); expect(tray).not.toContain('"waggle://pause-agents"'); expect(tray).not.toContain('"waggle://quit"'); expect(tray).not.toContain('"/about"'); }); it('web app mounts the Tauri desktop navigation bridge', () => { const app = fs.readFileSync(path.join(ROOT, 'apps', 'web', 'src', 'App.tsx'), 'utf-8'); expect(app).toContain('listenDesktopNavigation'); expect(app).toContain('listenDesktopShellEvents'); expect(app).toContain(''); }); it('tauri.conf.json has CSP that allows localhost connections', () => { const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); const csp = conf.app.security.csp; expect(csp).toContain('http://localhost:*'); expect(csp).toContain('ws://localhost:*'); }); it('Cargo.toml has updater plugin dependency', () => { const cargo = fs.readFileSync(path.join(TAURI_DIR, 'Cargo.toml'), 'utf-8'); expect(cargo).toContain('tauri-plugin-updater'); expect(cargo).toContain('tauri-plugin-notification'); expect(cargo).toContain('tauri-plugin-single-instance'); }); it('capabilities do not expose updater commands while updater config is disabled', () => { const caps = JSON.parse( fs.readFileSync(path.join(TAURI_DIR, 'capabilities', 'default.json'), 'utf-8'), ); expect(caps.permissions).not.toContain('updater:default'); }); it('does not configure unit-only Tauri plugins as objects', () => { // Regression: the packaged debug exe panicked during startup when // `plugins.dialog` was `{ open, save }`; tauri-plugin-dialog expects no // config payload when initialized with `tauri_plugin_dialog::init()`. const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); expect(conf.plugins?.dialog).toBeUndefined(); }); it('lib.rs does not initialize updater while config is disabled', () => { // Regression: registering tauri-plugin-updater without plugins.updater // config deserializes as null and panics before the desktop UI starts. const lib = fs.readFileSync(path.join(TAURI_DIR, 'src', 'lib.rs'), 'utf-8'); expect(lib).not.toContain('tauri_plugin_updater::Builder::new().build()'); expect(lib).not.toContain('.updater()'); }); it('NSIS installer template exists', () => { const nsis = fs.readFileSync(path.join(TAURI_DIR, 'nsis', 'installer.nsi'), 'utf-8'); expect(nsis).toContain('NSIS_HOOK_PREINSTALL'); expect(nsis).toContain('Desktop shortcut'); expect(nsis).toContain('Start Menu'); }); it('icon.ico exists', () => { expect(fs.existsSync(path.join(TAURI_DIR, 'icons', 'icon.ico'))).toBe(true); }); it('build-sidecar script exists', () => { const script = path.join(ROOT, 'scripts', 'build-sidecar.mjs'); expect(fs.existsSync(script)).toBe(true); const content = fs.readFileSync(script, 'utf-8'); expect(content).toContain('esbuild'); expect(content).toContain('service.ts'); expect(content).toContain('resources/service.js'); }); it('D12: the bundled sidecar is generated at build time, never tracked', () => { // beforeBuildCommand regenerates the bundle on EVERY build path — including // a raw `npx tauri build` that bypasses the npm scripts and CI steps. A // tracked copy goes stale silently; a binary shipping an old server is a // release-stopping defect class (UX-Refactor P4 / D12 ruling). const conf = JSON.parse(fs.readFileSync(path.join(TAURI_DIR, 'tauri.conf.json'), 'utf-8')); expect(conf.build.beforeBuildCommand).toContain('build-sidecar.mjs'); const gitignore = fs.readFileSync(path.join(ROOT, '.gitignore'), 'utf-8'); expect(gitignore).toContain('app/src-tauri/resources/service.js'); expect(gitignore).toContain('app/src-tauri/resources/service.js.map'); }); it('bundle-node defaults to the Node version that stages native deps', () => { const script = fs.readFileSync(path.join(ROOT, 'scripts', 'bundle-node.mjs'), 'utf-8'); expect(script).toContain( 'process.env.WAGGLE_BUNDLED_NODE_VERSION ?? process.versions.node', ); }); it('sidecar resource preflight checks bundled Node ABI compatibility', () => { const script = fs.readFileSync( path.join(ROOT, 'scripts', 'check-sidecar-resources.mjs'), 'utf-8', ); expect(script).toContain('process.versions.modules'); expect(script).toContain('execFileSync(nodePath'); }); it('staged sidecar resources have Windows MSI codepage-safe relative paths', () => { // WiX 3 links the en-US MSI with codepage 1252. Dependency test fixtures // with paths such as "snowman" Unicode names must be pruned before MSI // bundling or light.exe fails after the Rust build has already succeeded. const resources = path.join(TAURI_DIR, 'resources'); const unsafe = listFiles(resources) .map((file) => path.relative(resources, file)) .filter((file) => !isWindows1252PathSafe(file)); expect(unsafe).toEqual([]); }); it('staged sidecar node_modules is self-contained after MSI extraction', () => { // Running from app/src-tauri/resources can accidentally resolve missing // packages from the repo root node_modules. The installed MSI layout cannot. const resources = path.join(TAURI_DIR, 'resources'); const nodeModules = path.join(resources, 'node_modules'); const missing: string[] = []; for (const packageDir of listPackageDirs(nodeModules)) { const manifest = JSON.parse( fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'), ) as { name?: string; dependencies?: Record }; for (const dep of Object.keys(manifest.dependencies ?? {})) { if (STAGED_DEPENDENCY_ALLOWLIST.has(dep)) continue; if (!resolveWithinStagedResources(packageDir, dep, resources)) { missing.push(`${path.relative(nodeModules, packageDir)} -> ${dep}`); } } } expect(missing).toEqual([]); }); }); describe('CI/CD Configuration', () => { it('release workflow exists for Windows + macOS builds', () => { const workflow = path.join(ROOT, '.github', 'workflows', 'release.yml'); expect(fs.existsSync(workflow)).toBe(true); const content = fs.readFileSync(workflow, 'utf-8'); expect(content).toContain('build-windows'); expect(content).toContain('build-macos'); expect(content).toContain('tauri-action'); expect(content).toContain('aarch64-apple-darwin'); expect(content).toContain('x86_64-apple-darwin'); }); it('release workflow does NOT publish a broken (empty-signature) updater manifest', () => { // The update-manifest job was removed with the updater config: it published // latest.json with empty signatures, which every client would reject. Re-add // it together with real updater signing (see tauri.conf updater note). const workflow = fs.readFileSync( path.join(ROOT, '.github', 'workflows', 'release.yml'), 'utf-8', ); // No active `update-manifest:` job (a re-enable note in comments is fine). expect(workflow).not.toMatch(/^\s*update-manifest:/m); }); it('release workflow stages sidecar dependencies before packaging', () => { // P0-2: the packaged sidecar require()s esbuild-externalized deps that must // be staged into resources/node_modules or it dies with MODULE_NOT_FOUND. const workflow = fs.readFileSync( path.join(ROOT, '.github', 'workflows', 'release.yml'), 'utf-8', ); expect(workflow).toContain('stage-sidecar-deps'); }); it('release workflow builds packages before bundling the desktop sidecar', () => { // Release builds must follow the same package -> sidecar ordering as the // PR Tauri verification lane, otherwise tag artifacts can ship stale or // missing workspace dist outputs even when PR verification was green. const workflow = fs.readFileSync( path.join(ROOT, '.github', 'workflows', 'release.yml'), 'utf-8', ); const buildPackageIndexes = [...workflow.matchAll(/npm run build:packages/g)].map( (match) => match.index ?? -1, ); const sidecarIndexes = [...workflow.matchAll(/node scripts\/build-sidecar\.mjs/g)].map( (match) => match.index ?? -1, ); expect(buildPackageIndexes).toHaveLength(sidecarIndexes.length); expect(sidecarIndexes).toHaveLength(2); for (const [index, sidecarIndex] of sidecarIndexes.entries()) { expect(buildPackageIndexes[index]).toBeLessThan(sidecarIndex); } }); }); describe('Playwright Visual Regression Setup', () => { it('playwright.config.ts exists', () => { const conf = path.join(ROOT, 'playwright.config.ts'); expect(fs.existsSync(conf)).toBe(true); const content = fs.readFileSync(conf, 'utf-8'); expect(content).toContain('maxDiffPixelRatio'); expect(content).toContain('localhost:3333'); }); it('visual test spec exists with 14 test cases (7 views x 2 themes)', () => { const spec = path.join(ROOT, 'tests', 'visual', 'views.spec.ts'); expect(fs.existsSync(spec)).toBe(true); const content = fs.readFileSync(spec, 'utf-8'); expect(content).toContain('Dark Mode'); expect(content).toContain('Light Mode'); // 7 views defined expect(content).toContain('chat'); expect(content).toContain('memory'); expect(content).toContain('events'); expect(content).toContain('capabilities'); expect(content).toContain('cockpit'); expect(content).toContain('mission-control'); expect(content).toContain('settings'); }); });