From 55d7ba77009dbbba0eeb98684a455a1ebddfb49e Mon Sep 17 00:00:00 2001 From: Cascade Bot Date: Tue, 17 Mar 2026 18:44:28 +0000 Subject: [PATCH] test(cli): add unit tests for dashboard prompts, webhooklogs, and webhooks commands --- .../cli/dashboard/prompts/prompts.test.ts | 312 ++++++++++++++++++ .../dashboard/webhooklogs/webhooklogs.test.ts | 197 +++++++++++ .../cli/dashboard/webhooks/webhooks.test.ts | 312 ++++++++++++++++++ 3 files changed, 821 insertions(+) create mode 100644 tests/unit/cli/dashboard/prompts/prompts.test.ts create mode 100644 tests/unit/cli/dashboard/webhooklogs/webhooklogs.test.ts create mode 100644 tests/unit/cli/dashboard/webhooks/webhooks.test.ts diff --git a/tests/unit/cli/dashboard/prompts/prompts.test.ts b/tests/unit/cli/dashboard/prompts/prompts.test.ts new file mode 100644 index 00000000..c27a4be4 --- /dev/null +++ b/tests/unit/cli/dashboard/prompts/prompts.test.ts @@ -0,0 +1,312 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mockLoadConfig = vi.fn(); +const mockCreateDashboardClient = vi.fn(); + +vi.mock('../../../../../src/cli/dashboard/_shared/config.js', () => ({ + loadConfig: (...args: unknown[]) => mockLoadConfig(...args), +})); + +vi.mock('../../../../../src/cli/dashboard/_shared/client.js', () => ({ + createDashboardClient: (...args: unknown[]) => mockCreateDashboardClient(...args), +})); + +vi.mock('chalk', () => ({ + default: { + bold: (s: string) => s, + blue: (s: string) => s, + green: (s: string) => s, + red: (s: string) => s, + yellow: (s: string) => s, + dim: (s: string) => s, + }, +})); + +vi.mock('node:fs', () => ({ + readFileSync: vi.fn().mockReturnValue('template content'), +})); + +import PromptsDefaultPartial from '../../../../../src/cli/dashboard/prompts/default-partial.js'; +import PromptsDefault from '../../../../../src/cli/dashboard/prompts/default.js'; +import PromptsGetPartial from '../../../../../src/cli/dashboard/prompts/get-partial.js'; +import PromptsListPartials from '../../../../../src/cli/dashboard/prompts/list-partials.js'; +import PromptsResetPartial from '../../../../../src/cli/dashboard/prompts/reset-partial.js'; +import PromptsSetPartial from '../../../../../src/cli/dashboard/prompts/set-partial.js'; +import PromptsValidate from '../../../../../src/cli/dashboard/prompts/validate.js'; +import PromptsVariables from '../../../../../src/cli/dashboard/prompts/variables.js'; + +// oclif's Command.parse() calls this.config.runHook internally +const oclifConfig = { + runHook: vi.fn().mockResolvedValue({ successes: [], failures: [] }), +}; + +const baseConfig = { serverUrl: 'http://localhost:3001', sessionToken: 'tok' }; + +function makeClient(overrides: Record = {}) { + return { + prompts: { + getDefault: { query: vi.fn().mockResolvedValue({ content: 'default template content' }) }, + getDefaultPartial: { + query: vi.fn().mockResolvedValue({ content: 'default partial content' }), + }, + variables: { + query: vi + .fn() + .mockResolvedValue([ + { name: 'workItemTitle', group: 'work-item', description: 'Title of the work item' }, + ]), + }, + listPartials: { + query: vi.fn().mockResolvedValue([ + { name: 'git', source: 'disk', lines: 20 }, + { name: 'tmux', source: 'db', lines: 15 }, + ]), + }, + getPartial: { + query: vi + .fn() + .mockResolvedValue({ name: 'git', content: 'partial content', source: 'disk', id: null }), + }, + upsertPartial: { + mutate: vi.fn().mockResolvedValue({ id: 42, name: 'git' }), + }, + deletePartial: { + mutate: vi.fn().mockResolvedValue(undefined), + }, + validate: { + mutate: vi.fn().mockResolvedValue({ valid: true }), + }, + }, + ...overrides, + }; +} + +describe('PromptsDefault (prompts default)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('queries default template for given agent-type', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsDefault(['--agent-type', 'implementation'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.getDefault.query).toHaveBeenCalledWith({ + agentType: 'implementation', + }); + }); + + it('requires --agent-type flag', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new PromptsDefault([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); + +describe('PromptsDefaultPartial (prompts default-partial)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('queries default partial content for given name', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsDefaultPartial(['--name', 'git'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.getDefaultPartial.query).toHaveBeenCalledWith({ name: 'git' }); + }); + + it('requires --name flag', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new PromptsDefaultPartial([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); + +describe('PromptsVariables (prompts variables)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('lists available template variables', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsVariables([], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.variables.query).toHaveBeenCalled(); + }); + + it('outputs json when --json flag is set', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsVariables(['--json'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.variables.query).toHaveBeenCalled(); + }); +}); + +describe('PromptsListPartials (prompts list-partials)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('queries all partials and outputs table', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsListPartials([], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.listPartials.query).toHaveBeenCalled(); + }); + + it('outputs json when --json flag is set', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsListPartials(['--json'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.listPartials.query).toHaveBeenCalled(); + }); +}); + +describe('PromptsGetPartial (prompts get-partial)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('queries partial content for given name', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsGetPartial(['--name', 'git'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.getPartial.query).toHaveBeenCalledWith({ name: 'git' }); + }); + + it('requires --name flag', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new PromptsGetPartial([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); + +describe('PromptsSetPartial (prompts set-partial)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('upserts partial with name and file content', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsSetPartial( + ['--name', 'git', '--file', '/some/path/git.eta'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.prompts.upsertPartial.mutate).toHaveBeenCalledWith({ + name: 'git', + content: 'template content', + }); + }); + + it('requires --name and --file flags', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new PromptsSetPartial(['--name', 'git'], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); + +describe('PromptsResetPartial (prompts reset-partial)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('deletes a DB partial to revert to disk default', async () => { + const client = makeClient(); + (client.prompts.getPartial.query as ReturnType).mockResolvedValue({ + name: 'git', + content: 'db content', + source: 'db', + id: 42, + }); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsResetPartial(['--name', 'git'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.getPartial.query).toHaveBeenCalledWith({ name: 'git' }); + expect(client.prompts.deletePartial.mutate).toHaveBeenCalledWith({ id: 42 }); + }); + + it('skips deletion when partial is already using disk default', async () => { + const client = makeClient(); + // Default mock already returns source: 'disk' + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsResetPartial(['--name', 'tmux'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.getPartial.query).toHaveBeenCalledWith({ name: 'tmux' }); + expect(client.prompts.deletePartial.mutate).not.toHaveBeenCalled(); + }); + + it('requires --name flag', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new PromptsResetPartial([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); + +describe('PromptsValidate (prompts validate)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('validates template from file and reports valid', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsValidate(['--file', '/some/path/template.eta'], oclifConfig as never); + await cmd.run(); + + expect(client.prompts.validate.mutate).toHaveBeenCalledWith({ template: 'template content' }); + }); + + it('outputs json when --json flag is set', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new PromptsValidate( + ['--file', '/some/path/template.eta', '--json'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.prompts.validate.mutate).toHaveBeenCalledWith({ template: 'template content' }); + }); + + it('requires --file flag', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new PromptsValidate([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); diff --git a/tests/unit/cli/dashboard/webhooklogs/webhooklogs.test.ts b/tests/unit/cli/dashboard/webhooklogs/webhooklogs.test.ts new file mode 100644 index 00000000..c559f827 --- /dev/null +++ b/tests/unit/cli/dashboard/webhooklogs/webhooklogs.test.ts @@ -0,0 +1,197 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mockLoadConfig = vi.fn(); +const mockCreateDashboardClient = vi.fn(); + +vi.mock('../../../../../src/cli/dashboard/_shared/config.js', () => ({ + loadConfig: (...args: unknown[]) => mockLoadConfig(...args), +})); + +vi.mock('../../../../../src/cli/dashboard/_shared/client.js', () => ({ + createDashboardClient: (...args: unknown[]) => mockCreateDashboardClient(...args), +})); + +vi.mock('chalk', () => ({ + default: { + bold: (s: string) => s, + blue: (s: string) => s, + green: (s: string) => s, + red: (s: string) => s, + yellow: (s: string) => s, + dim: (s: string) => s, + }, +})); + +import WebhookLogsList from '../../../../../src/cli/dashboard/webhooklogs/list.js'; +import WebhookLogsShow from '../../../../../src/cli/dashboard/webhooklogs/show.js'; + +// oclif's Command.parse() calls this.config.runHook internally +const oclifConfig = { + runHook: vi.fn().mockResolvedValue({ successes: [], failures: [] }), +}; + +const baseConfig = { serverUrl: 'http://localhost:3001', sessionToken: 'tok' }; + +const sampleLog = { + id: 'abc12345-0000-0000-0000-000000000000', + source: 'github', + method: 'POST', + path: '/webhook/github', + eventType: 'push', + statusCode: 200, + processed: true, + projectId: 'my-project', + decisionReason: 'handled', + receivedAt: new Date('2024-01-01T00:00:00Z'), + headers: { 'x-github-event': 'push' }, + body: { action: 'push' }, + bodyRaw: null, +}; + +function makeClient(overrides: Record = {}) { + return { + webhookLogs: { + list: { + query: vi.fn().mockResolvedValue({ data: [sampleLog], total: 1 }), + }, + getById: { + query: vi.fn().mockResolvedValue(sampleLog), + }, + }, + ...overrides, + }; +} + +describe('WebhookLogsList (webhooklogs list)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('lists webhook logs with default parameters', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsList([], oclifConfig as never); + await cmd.run(); + + expect(client.webhookLogs.list.query).toHaveBeenCalledWith({ + source: undefined, + eventType: undefined, + limit: 50, + offset: 0, + }); + }); + + it('passes --source filter to query', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsList(['--source', 'github'], oclifConfig as never); + await cmd.run(); + + expect(client.webhookLogs.list.query).toHaveBeenCalledWith({ + source: 'github', + eventType: undefined, + limit: 50, + offset: 0, + }); + }); + + it('passes --event-type filter to query', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsList(['--event-type', 'push'], oclifConfig as never); + await cmd.run(); + + expect(client.webhookLogs.list.query).toHaveBeenCalledWith({ + source: undefined, + eventType: 'push', + limit: 50, + offset: 0, + }); + }); + + it('passes --limit filter to query', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsList(['--limit', '10'], oclifConfig as never); + await cmd.run(); + + expect(client.webhookLogs.list.query).toHaveBeenCalledWith({ + source: undefined, + eventType: undefined, + limit: 10, + offset: 0, + }); + }); + + it('passes combined filters to query', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsList( + ['--source', 'trello', '--event-type', 'card-moved', '--limit', '20'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhookLogs.list.query).toHaveBeenCalledWith({ + source: 'trello', + eventType: 'card-moved', + limit: 20, + offset: 0, + }); + }); + + it('outputs json when --json flag is set', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsList(['--json'], oclifConfig as never); + await cmd.run(); + + expect(client.webhookLogs.list.query).toHaveBeenCalled(); + }); +}); + +describe('WebhookLogsShow (webhooklogs show)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('queries log by ID and displays detail', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsShow(['abc12345-0000-0000-0000-000000000000'], oclifConfig as never); + await cmd.run(); + + expect(client.webhookLogs.getById.query).toHaveBeenCalledWith({ + id: 'abc12345-0000-0000-0000-000000000000', + }); + }); + + it('outputs json when --json flag is set', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhookLogsShow( + ['abc12345-0000-0000-0000-000000000000', '--json'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhookLogs.getById.query).toHaveBeenCalledWith({ + id: 'abc12345-0000-0000-0000-000000000000', + }); + }); + + it('requires a log ID argument', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new WebhookLogsShow([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); diff --git a/tests/unit/cli/dashboard/webhooks/webhooks.test.ts b/tests/unit/cli/dashboard/webhooks/webhooks.test.ts new file mode 100644 index 00000000..bef9b7c8 --- /dev/null +++ b/tests/unit/cli/dashboard/webhooks/webhooks.test.ts @@ -0,0 +1,312 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mockLoadConfig = vi.fn(); +const mockCreateDashboardClient = vi.fn(); + +vi.mock('../../../../../src/cli/dashboard/_shared/config.js', () => ({ + loadConfig: (...args: unknown[]) => mockLoadConfig(...args), +})); + +vi.mock('../../../../../src/cli/dashboard/_shared/client.js', () => ({ + createDashboardClient: (...args: unknown[]) => mockCreateDashboardClient(...args), +})); + +vi.mock('chalk', () => ({ + default: { + bold: (s: string) => s, + blue: (s: string) => s, + green: (s: string) => s, + red: (s: string) => s, + yellow: (s: string) => s, + dim: (s: string) => s, + }, +})); + +import WebhooksCreate from '../../../../../src/cli/dashboard/webhooks/create.js'; +import WebhooksDelete from '../../../../../src/cli/dashboard/webhooks/delete.js'; +import WebhooksList from '../../../../../src/cli/dashboard/webhooks/list.js'; + +// oclif's Command.parse() calls this.config.runHook internally +const oclifConfig = { + runHook: vi.fn().mockResolvedValue({ successes: [], failures: [] }), +}; + +const baseConfig = { serverUrl: 'http://localhost:3001', sessionToken: 'tok' }; + +function makeClient(overrides: Record = {}) { + return { + webhooks: { + list: { + query: vi.fn().mockResolvedValue({ + trello: [], + github: [], + jira: [], + errors: {}, + }), + }, + create: { + mutate: vi.fn().mockResolvedValue({ + trello: { id: 'trello-wh-1', callbackURL: 'http://localhost:3001/webhook/trello' }, + github: { id: 123, config: { url: 'http://localhost:3001/webhook/github' } }, + jira: null, + }), + }, + delete: { + mutate: vi.fn().mockResolvedValue({ + trello: ['trello-wh-1'], + github: [123], + jira: [], + }), + }, + }, + ...overrides, + }; +} + +describe('WebhooksList (webhooks list)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('lists webhooks for project ID', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksList(['my-project'], oclifConfig as never); + await cmd.run(); + + expect(client.webhooks.list.query).toHaveBeenCalledWith({ + projectId: 'my-project', + oneTimeTokens: undefined, + }); + }); + + it('passes --github-token as oneTimeTokens when provided', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksList( + ['my-project', '--github-token', 'ghp_testtoken123'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhooks.list.query).toHaveBeenCalledWith({ + projectId: 'my-project', + oneTimeTokens: { github: 'ghp_testtoken123' }, + }); + }); + + it('passes multiple one-time tokens when provided', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksList( + [ + 'my-project', + '--github-token', + 'ghp_testtoken123', + '--trello-api-key', + 'trello-key', + '--trello-token', + 'trello-token', + ], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhooks.list.query).toHaveBeenCalledWith({ + projectId: 'my-project', + oneTimeTokens: { + github: 'ghp_testtoken123', + trelloApiKey: 'trello-key', + trelloToken: 'trello-token', + }, + }); + }); + + it('outputs json when --json flag is set', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksList(['my-project', '--json'], oclifConfig as never); + await cmd.run(); + + expect(client.webhooks.list.query).toHaveBeenCalled(); + }); + + it('requires project ID argument', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new WebhooksList([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); + +describe('WebhooksCreate (webhooks create)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('creates webhooks for project ID using server URL as callback base', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksCreate(['my-project'], oclifConfig as never); + await cmd.run(); + + expect(client.webhooks.create.mutate).toHaveBeenCalledWith({ + projectId: 'my-project', + callbackBaseUrl: baseConfig.serverUrl, + trelloOnly: false, + githubOnly: false, + oneTimeTokens: undefined, + }); + }); + + it('passes --callback-url when provided', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksCreate( + ['my-project', '--callback-url', 'https://cascade.example.com'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhooks.create.mutate).toHaveBeenCalledWith({ + projectId: 'my-project', + callbackBaseUrl: 'https://cascade.example.com', + trelloOnly: false, + githubOnly: false, + oneTimeTokens: undefined, + }); + }); + + it('passes --github-token as oneTimeTokens when provided', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksCreate( + ['my-project', '--github-token', 'ghp_testtoken123'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhooks.create.mutate).toHaveBeenCalledWith({ + projectId: 'my-project', + callbackBaseUrl: baseConfig.serverUrl, + trelloOnly: false, + githubOnly: false, + oneTimeTokens: { github: 'ghp_testtoken123' }, + }); + }); + + it('passes --trello-only flag correctly', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksCreate(['my-project', '--trello-only'], oclifConfig as never); + await cmd.run(); + + expect(client.webhooks.create.mutate).toHaveBeenCalledWith( + expect.objectContaining({ trelloOnly: true, githubOnly: false }), + ); + }); + + it('requires project ID argument', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new WebhooksCreate([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +}); + +describe('WebhooksDelete (webhooks delete)', () => { + beforeEach(() => { + mockLoadConfig.mockReturnValue(baseConfig); + }); + + it('deletes webhooks for project ID using server URL as callback base', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksDelete(['my-project'], oclifConfig as never); + await cmd.run(); + + expect(client.webhooks.delete.mutate).toHaveBeenCalledWith({ + projectId: 'my-project', + callbackBaseUrl: baseConfig.serverUrl, + trelloOnly: false, + githubOnly: false, + oneTimeTokens: undefined, + }); + }); + + it('passes --callback-url when provided', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksDelete( + ['my-project', '--callback-url', 'https://cascade.example.com'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhooks.delete.mutate).toHaveBeenCalledWith({ + projectId: 'my-project', + callbackBaseUrl: 'https://cascade.example.com', + trelloOnly: false, + githubOnly: false, + oneTimeTokens: undefined, + }); + }); + + it('passes --github-token as oneTimeTokens when provided', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksDelete( + ['my-project', '--github-token', 'ghp_testtoken123'], + oclifConfig as never, + ); + await cmd.run(); + + expect(client.webhooks.delete.mutate).toHaveBeenCalledWith({ + projectId: 'my-project', + callbackBaseUrl: baseConfig.serverUrl, + trelloOnly: false, + githubOnly: false, + oneTimeTokens: { github: 'ghp_testtoken123' }, + }); + }); + + it('passes --github-only flag correctly', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksDelete(['my-project', '--github-only'], oclifConfig as never); + await cmd.run(); + + expect(client.webhooks.delete.mutate).toHaveBeenCalledWith( + expect.objectContaining({ trelloOnly: false, githubOnly: true }), + ); + }); + + it('outputs json when --json flag is set', async () => { + const client = makeClient(); + mockCreateDashboardClient.mockReturnValue(client); + + const cmd = new WebhooksDelete(['my-project', '--json'], oclifConfig as never); + await cmd.run(); + + expect(client.webhooks.delete.mutate).toHaveBeenCalled(); + }); + + it('requires project ID argument', async () => { + mockCreateDashboardClient.mockReturnValue(makeClient()); + + const cmd = new WebhooksDelete([], oclifConfig as never); + await expect(cmd.run()).rejects.toThrow(); + }); +});