// update-check.test.mjs — Tests for hooks/scripts/update-check.mjs // Zero external dependencies: node:test + node:assert only. import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { resolve } from 'node:path'; import { runHook, runHookWithEnv } from './hook-helper.mjs'; const SCRIPT = resolve(import.meta.dirname, '../../hooks/scripts/update-check.mjs'); // --------------------------------------------------------------------------- // Unit tests for isNewer (imported directly) // --------------------------------------------------------------------------- import { isNewer, CHECK_INTERVAL_MS } from '../../hooks/scripts/update-check.mjs'; describe('isNewer — semver comparison', () => { it('returns true when remote patch is higher', () => { assert.equal(isNewer('2.8.1', '2.8.0'), true); }); it('returns false when versions are equal', () => { assert.equal(isNewer('2.8.0', '2.8.0'), false); }); it('returns false when remote is older', () => { assert.equal(isNewer('2.7.9', '2.8.0'), false); }); it('returns true when remote major is higher', () => { assert.equal(isNewer('3.0.0', '2.99.99'), true); }); it('returns true when remote minor is higher', () => { assert.equal(isNewer('2.9.0', '2.8.99'), true); }); it('handles different length versions', () => { assert.equal(isNewer('2.8.1', '2.8'), true); assert.equal(isNewer('2.8', '2.8.0'), false); }); }); describe('CHECK_INTERVAL_MS', () => { it('is 24 hours in milliseconds', () => { assert.equal(CHECK_INTERVAL_MS, 86_400_000); }); }); // --------------------------------------------------------------------------- // Integration tests (subprocess via hook-helper) // --------------------------------------------------------------------------- describe('update-check hook — opt-out', () => { it('exits silently when LLM_SECURITY_UPDATE_CHECK=off', async () => { const result = await runHookWithEnv(SCRIPT, '{}', { LLM_SECURITY_UPDATE_CHECK: 'off', }); assert.equal(result.code, 0); assert.equal(result.stdout.trim(), ''); }); }); describe('update-check hook — graceful failures', () => { it('exits 0 with empty stdin', async () => { const result = await runHook(SCRIPT, ''); assert.equal(result.code, 0); }); it('exits 0 with valid JSON stdin (no CLAUDE_PLUGIN_ROOT → fails to read plugin.json)', async () => { // Without CLAUDE_PLUGIN_ROOT set to a valid plugin, it will fail to // read plugin.json from the default path and exit 0 silently. const result = await runHookWithEnv(SCRIPT, '{}', { CLAUDE_PLUGIN_ROOT: '/nonexistent/path', }); assert.equal(result.code, 0); assert.equal(result.stdout.trim(), ''); }); });