diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts index 3b7e917..e468b80 100644 --- a/src/__tests__/config.test.ts +++ b/src/__tests__/config.test.ts @@ -22,6 +22,16 @@ describe('config command', () => { let consoleLogSpy: any; let consoleErrorSpy: any; + const consoleLogOrder = (matcher: RegExp | string) => { + const callIndex = consoleLogSpy.mock.calls.findIndex(([message]: [string]) => + matcher instanceof RegExp ? matcher.test(message) : message.includes(matcher), + ); + expect(callIndex).toBeGreaterThanOrEqual(0); + return consoleLogSpy.mock.invocationCallOrder[callIndex]; + }; + + const firstInputOrder = () => vi.mocked(tui.input).mock.invocationCallOrder[0]; + beforeEach(() => { vi.clearAllMocks(); program = new Command(); @@ -62,6 +72,21 @@ describe('config command', () => { expect(configUtils.clearNotificationCredentials).toHaveBeenCalled(); expect(configUtils.setConfig).toHaveBeenCalledWith('notification_service', 'discord'); expect(configUtils.setConfig).toHaveBeenCalledWith('discord_webhook', 'https://discord.com/api/webhooks/123456789/token-here'); + expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringMatching(/Discord webhook setup/i)); + expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Integrations > Webhooks')); + + const guideOrder = consoleLogOrder(/Discord webhook setup/i); + expect(guideOrder).toBeLessThan(firstInputOrder()); + }); + + it('should validate Discord webhook URLs during setup', async () => { + vi.mocked(tui.select).mockResolvedValue('discord'); + vi.mocked(tui.input).mockResolvedValue('https://discord.com/api/webhooks/123456789/token-here'); + + await program.parseAsync(['node', 'test', 'config', 'setup']); + + const webhookPrompt = vi.mocked(tui.input).mock.calls[0][0]; + expect(webhookPrompt.validate('not-a-webhook')).toBe('Must be a valid Discord webhook URL (including ID and Token)'); }); it('should call select, multiple inputs and setConfig on email setup', async () => { @@ -79,6 +104,25 @@ describe('config command', () => { expect(configUtils.setConfig).toHaveBeenCalledWith('email_port', 587); expect(configUtils.setConfig).toHaveBeenCalledWith('email_user', 'user@test.com'); expect(configUtils.setConfig).toHaveBeenCalledWith('email_to', 'to@test.com'); + expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringMatching(/Email SMTP setup/i)); + expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('KDM_SMTP_PASSWORD')); + + const guideOrder = consoleLogOrder(/Email SMTP setup/i); + expect(guideOrder).toBeLessThan(firstInputOrder()); + }); + + it('should require an SMTP host during email setup', async () => { + vi.mocked(tui.select).mockResolvedValue('email'); + vi.mocked(tui.input) + .mockResolvedValueOnce('smtp.gmail.com') + .mockResolvedValueOnce('587') + .mockResolvedValueOnce('user@test.com') + .mockResolvedValueOnce('to@test.com'); + + await program.parseAsync(['node', 'test', 'config', 'setup']); + + const smtpHostPrompt = vi.mocked(tui.input).mock.calls[0][0]; + expect(smtpHostPrompt.validate('')).toBe('Host is required'); }); it('should call setConfig on config set', async () => { diff --git a/src/commands/config.ts b/src/commands/config.ts index 50c515f..939924c 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -30,6 +30,8 @@ export const registerConfigCommand = (program: Command) => { } if (choice === 'discord') { + printDiscordWebhookGuide(); + const webhook = await input({ message: 'Discord Webhook URL:', validate: (v) => { @@ -43,6 +45,8 @@ export const registerConfigCommand = (program: Command) => { setConfig('notification_service', 'discord'); console.log(chalk.green('\n✓ Discord Webhook configured.')); } else if (choice === 'email') { + printEmailSmtpGuide(); + const host = await input({ message: 'SMTP Host:', placeholder: 'smtp.gmail.com', @@ -73,7 +77,6 @@ export const registerConfigCommand = (program: Command) => { setConfig('notification_service', 'email'); console.log(chalk.green('\n✓ Email SMTP configured.')); - console.log(chalk.yellow('! Important: Set your SMTP password in the KDM_SMTP_PASSWORD environment variable for notifications to work.')); } console.log(chalk.green(`✓ Notification service set to: ${chalk.bold(choice.toUpperCase())}`)); @@ -128,3 +131,25 @@ export const registerConfigCommand = (program: Command) => { console.log(chalk.green('✓ Configuration cleared.')); }); }; + +const printDiscordWebhookGuide = () => { + console.log(chalk.gray('\n──────────────────────────────────────────────────')); + console.log(chalk.cyan('Discord webhook setup')); + console.log(chalk.white(' 1. Open your Discord server settings.')); + console.log(chalk.white(' 2. Go to Integrations > Webhooks.')); + console.log(chalk.white(' 3. Create a new webhook and choose the alert channel.')); + console.log(chalk.white(' 4. Copy the webhook URL and paste it below.')); + console.log(chalk.dim(' The URL should start with https://discord.com/api/webhooks/.')); + console.log(chalk.gray('──────────────────────────────────────────────────\n')); +}; + +const printEmailSmtpGuide = () => { + console.log(chalk.gray('\n──────────────────────────────────────────────────')); + console.log(chalk.cyan('Email SMTP setup')); + console.log(chalk.white(' 1. Find your provider SMTP settings before continuing.')); + console.log(chalk.white(' 2. Common hosts: smtp.gmail.com for Gmail, smtp.office365.com for Outlook.')); + console.log(chalk.white(' 3. Use port 587 for STARTTLS unless your provider says otherwise.')); + console.log(chalk.white(' 4. Set the SMTP password in KDM_SMTP_PASSWORD before sending alerts.')); + console.log(chalk.dim(' Gmail accounts with 2FA usually require an App Password.')); + console.log(chalk.gray('──────────────────────────────────────────────────\n')); +};