Skip to main content
Glama
acknowledgment.test.ts10.1 kB
/** * Tests for JIT Safety Acknowledgment Module * * @package WP_Navigator_MCP * @since 2.4.0 */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import * as fs from 'fs'; import * as path from 'path'; import { loadSafetyState, saveSafetyState, resetSafetyState, checkSafetyAcknowledgment, type SafetyState, type SafetyCheckOptions, } from './acknowledgment.js'; // Mock fs module vi.mock('fs'); vi.mock('../tui/prompts.js', () => ({ confirmPrompt: vi.fn(), })); vi.mock('../tui/components.js', () => ({ warning: vi.fn(), info: vi.fn(), newline: vi.fn(), box: vi.fn(), colorize: vi.fn((text: string) => text), list: vi.fn(), })); // Helper to get prompts mock import { confirmPrompt } from '../tui/prompts.js'; const mockConfirmPrompt = vi.mocked(confirmPrompt); describe('JIT Safety Acknowledgment', () => { const testDir = '/test/project'; const stateDir = path.join(testDir, '.wpnav'); const statePath = path.join(stateDir, 'state.json'); beforeEach(() => { vi.clearAllMocks(); // Default fs mocks vi.mocked(fs.existsSync).mockReturnValue(false); vi.mocked(fs.readFileSync).mockReturnValue('{}'); vi.mocked(fs.writeFileSync).mockImplementation(() => {}); vi.mocked(fs.mkdirSync).mockImplementation(() => undefined); }); afterEach(() => { vi.restoreAllMocks(); }); describe('loadSafetyState', () => { it('returns default state when file does not exist', () => { vi.mocked(fs.existsSync).mockReturnValue(false); const state = loadSafetyState(testDir); expect(state.first_sync_acknowledged).toBe(false); expect(state.acknowledged_at).toBeUndefined(); }); it('loads state from nested safety object', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue( JSON.stringify({ safety: { first_sync_acknowledged: true, acknowledged_at: '2025-12-14T10:00:00.000Z', }, }) ); const state = loadSafetyState(testDir); expect(state.first_sync_acknowledged).toBe(true); expect(state.acknowledged_at).toBe('2025-12-14T10:00:00.000Z'); }); it('handles legacy top-level state format', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue( JSON.stringify({ first_sync_acknowledged: true, acknowledged_at: '2025-12-14T10:00:00.000Z', }) ); const state = loadSafetyState(testDir); expect(state.first_sync_acknowledged).toBe(true); }); it('returns default state on parse error', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue('invalid json'); const state = loadSafetyState(testDir); expect(state.first_sync_acknowledged).toBe(false); }); }); describe('saveSafetyState', () => { it('creates state directory if it does not exist', () => { vi.mocked(fs.existsSync).mockReturnValue(false); const state: SafetyState = { first_sync_acknowledged: true, acknowledged_at: '2025-12-14T10:00:00.000Z', }; saveSafetyState(testDir, state); expect(fs.mkdirSync).toHaveBeenCalledWith(stateDir, { recursive: true }); }); it('merges with existing state', () => { vi.mocked(fs.existsSync).mockImplementation((p) => p === statePath); vi.mocked(fs.readFileSync).mockReturnValue( JSON.stringify({ last_daily_reminder: '2025-12-13T10:00:00.000Z', }) ); const state: SafetyState = { first_sync_acknowledged: true, acknowledged_at: '2025-12-14T10:00:00.000Z', }; saveSafetyState(testDir, state); expect(fs.writeFileSync).toHaveBeenCalledWith( statePath, expect.stringContaining('last_daily_reminder'), 'utf-8' ); expect(fs.writeFileSync).toHaveBeenCalledWith( statePath, expect.stringContaining('first_sync_acknowledged'), 'utf-8' ); }); it('writes state to correct path', () => { vi.mocked(fs.existsSync).mockReturnValue(false); const state: SafetyState = { first_sync_acknowledged: true, }; saveSafetyState(testDir, state); expect(fs.writeFileSync).toHaveBeenCalledWith(statePath, expect.any(String), 'utf-8'); }); }); describe('resetSafetyState', () => { it('removes safety section from state file', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue( JSON.stringify({ safety: { first_sync_acknowledged: true }, other_data: 'preserved', }) ); resetSafetyState(testDir); const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0]; const written = JSON.parse(writeCall[1] as string); expect(written.safety).toBeUndefined(); expect(written.other_data).toBe('preserved'); }); it('does nothing if state file does not exist', () => { vi.mocked(fs.existsSync).mockReturnValue(false); resetSafetyState(testDir); expect(fs.writeFileSync).not.toHaveBeenCalled(); }); }); describe('checkSafetyAcknowledgment', () => { const baseOptions: SafetyCheckOptions = { projectDir: testDir, skipConfirm: false, isPro: false, siteName: 'example.com', dryRun: false, isInteractive: true, }; it('skips prompt for dry-run mode', async () => { const result = await checkSafetyAcknowledgment({ ...baseOptions, dryRun: true, }); expect(result.shown).toBe(false); expect(result.confirmed).toBe(true); expect(result.skipReason).toBe('dry_run'); expect(mockConfirmPrompt).not.toHaveBeenCalled(); }); it('skips prompt when --yes flag is set', async () => { const result = await checkSafetyAcknowledgment({ ...baseOptions, skipConfirm: true, }); expect(result.shown).toBe(false); expect(result.confirmed).toBe(true); expect(result.skipReason).toBe('yes_flag'); expect(mockConfirmPrompt).not.toHaveBeenCalled(); }); it('skips prompt when already acknowledged', async () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue( JSON.stringify({ safety: { first_sync_acknowledged: true }, }) ); const result = await checkSafetyAcknowledgment(baseOptions); expect(result.shown).toBe(false); expect(result.confirmed).toBe(true); expect(result.skipReason).toBe('already_acknowledged'); expect(mockConfirmPrompt).not.toHaveBeenCalled(); }); it('skips prompt in non-interactive mode', async () => { const result = await checkSafetyAcknowledgment({ ...baseOptions, isInteractive: false, }); expect(result.shown).toBe(false); expect(result.confirmed).toBe(true); expect(result.skipReason).toBe('non_interactive'); expect(mockConfirmPrompt).not.toHaveBeenCalled(); }); it('shows prompt and saves acknowledgment when confirmed', async () => { mockConfirmPrompt.mockResolvedValue(true); // User confirms backup const result = await checkSafetyAcknowledgment(baseOptions); expect(result.shown).toBe(true); expect(result.confirmed).toBe(true); expect(fs.writeFileSync).toHaveBeenCalled(); }); it('returns cancelled when user declines backup and continue', async () => { mockConfirmPrompt .mockResolvedValueOnce(false) // No backup .mockResolvedValueOnce(false); // Don't continue anyway const result = await checkSafetyAcknowledgment(baseOptions); expect(result.shown).toBe(true); expect(result.confirmed).toBe(false); expect(result.createRollback).toBe(false); }); it('continues when user has no backup but chooses to proceed', async () => { mockConfirmPrompt .mockResolvedValueOnce(false) // No backup .mockResolvedValueOnce(true); // Continue anyway const result = await checkSafetyAcknowledgment(baseOptions); expect(result.shown).toBe(true); expect(result.confirmed).toBe(true); }); it('offers rollback point for Pro users', async () => { mockConfirmPrompt .mockResolvedValueOnce(true) // Has backup .mockResolvedValueOnce(true); // Create rollback const result = await checkSafetyAcknowledgment({ ...baseOptions, isPro: true, }); expect(result.shown).toBe(true); expect(result.confirmed).toBe(true); expect(result.createRollback).toBe(true); }); it('does not offer rollback for Free users', async () => { mockConfirmPrompt.mockResolvedValueOnce(true); // Has backup const result = await checkSafetyAcknowledgment({ ...baseOptions, isPro: false, }); expect(result.shown).toBe(true); expect(result.confirmed).toBe(true); expect(result.createRollback).toBe(false); // Only one prompt for backup, no rollback prompt expect(mockConfirmPrompt).toHaveBeenCalledTimes(1); }); it('Pro user can decline rollback point', async () => { mockConfirmPrompt .mockResolvedValueOnce(true) // Has backup .mockResolvedValueOnce(false); // Don't create rollback const result = await checkSafetyAcknowledgment({ ...baseOptions, isPro: true, }); expect(result.shown).toBe(true); expect(result.confirmed).toBe(true); expect(result.createRollback).toBe(false); }); }); describe('state file path', () => { it('uses .wpnav/state.json within project directory', () => { vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue('{}'); loadSafetyState('/my/project'); expect(fs.existsSync).toHaveBeenCalledWith('/my/project/.wpnav/state.json'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server