From ddee98d0eb71b0e2426accd8b8e07dccf9b56b2f Mon Sep 17 00:00:00 2001 From: Cascade Bot Date: Mon, 23 Mar 2026 17:05:14 +0000 Subject: [PATCH] refactor(tests): migrate trigger tests to shared mocks and factories --- .../triggers/backlog-status-changed.test.ts | 99 ++---- .../unit/triggers/check-suite-failure.test.ts | 55 ++- .../unit/triggers/check-suite-success.test.ts | 28 +- .../github-pr-comment-mention.test.ts | 24 +- tests/unit/triggers/jira-label-added.test.ts | 48 +-- .../unit/triggers/jira-status-changed.test.ts | 25 +- tests/unit/triggers/label-added.test.ts | 185 ++++------- .../triggers/pr-conflict-detected.test.ts | 37 +-- tests/unit/triggers/pr-merged.test.ts | 55 +-- tests/unit/triggers/pr-opened.test.ts | 16 +- tests/unit/triggers/pr-ready-to-merge.test.ts | 312 +++--------------- .../unit/triggers/pr-review-submitted.test.ts | 40 +-- tests/unit/triggers/review-requested.test.ts | 15 +- tests/unit/triggers/status-changed.test.ts | 109 ++---- .../triggers/trello-comment-mention.test.ts | 24 +- 15 files changed, 316 insertions(+), 756 deletions(-) diff --git a/tests/unit/triggers/backlog-status-changed.test.ts b/tests/unit/triggers/backlog-status-changed.test.ts index cab06c29..0cc965e6 100644 --- a/tests/unit/triggers/backlog-status-changed.test.ts +++ b/tests/unit/triggers/backlog-status-changed.test.ts @@ -1,55 +1,33 @@ import { describe, expect, it, vi } from 'vitest'; - -vi.mock('../../../src/utils/logging.js', () => ({ - logger: { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }, -})); - -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); +import { + mockAcknowledgmentsModule, + mockConfigProvider, + mockConfigResolverModule, + mockJiraClientModule, + mockLogger, + mockReactionsModule, + mockTrelloClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; + +vi.mock('../../../src/utils/logging.js', () => ({ logger: mockLogger })); + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); // Mocks required for PM integration registration (pm/index.js side-effect) -vi.mock('../../../src/config/provider.js', () => ({ - getIntegrationCredential: vi.fn(), - loadProjectConfigByBoardId: vi.fn(), - loadProjectConfigByJiraProjectKey: vi.fn(), - findProjectById: vi.fn(), -})); -vi.mock('../../../src/trello/client.js', () => ({ - withTrelloCredentials: vi.fn(), - trelloClient: { getCard: vi.fn() }, -})); -vi.mock('../../../src/jira/client.js', () => ({ - withJiraCredentials: vi.fn(), - jiraClient: {}, -})); -vi.mock('../../../src/router/acknowledgments.js', () => ({ - postTrelloAck: vi.fn(), - deleteTrelloAck: vi.fn(), - resolveTrelloBotMemberId: vi.fn(), - postJiraAck: vi.fn(), - deleteJiraAck: vi.fn(), - resolveJiraBotAccountId: vi.fn(), -})); -vi.mock('../../../src/router/reactions.js', () => ({ - sendAcknowledgeReaction: vi.fn(), -})); +vi.mock('../../../src/config/provider.js', () => mockConfigProvider); +vi.mock('../../../src/trello/client.js', () => mockTrelloClientModule); +vi.mock('../../../src/jira/client.js', () => mockJiraClientModule); +vi.mock('../../../src/router/acknowledgments.js', () => mockAcknowledgmentsModule); +vi.mock('../../../src/router/reactions.js', () => mockReactionsModule); // Register PM integrations in the registry import '../../../src/pm/index.js'; import { TrelloStatusChangedBacklogTrigger } from '../../../src/triggers/trello/status-changed.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; +import { createMockProject, createTrelloActionPayload } from '../../helpers/factories.js'; describe('TrelloStatusChangedBacklogTrigger', () => { const trigger = TrelloStatusChangedBacklogTrigger; @@ -71,8 +49,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -84,7 +61,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { listAfter: { id: 'backlog-list-id', name: 'Backlog' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(true); @@ -94,8 +71,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -107,7 +83,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { listAfter: { id: 'backlog-list-id', name: 'Backlog' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -117,8 +93,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -129,7 +104,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { list: { id: 'backlog-list-id', name: 'Backlog' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(true); @@ -139,8 +114,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -152,7 +126,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { listAfter: { id: 'todo-list-id', name: 'TODO' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -163,8 +137,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { const ctx: TriggerContext = { project: projectWithoutBacklog, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -176,7 +149,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { listAfter: { id: 'backlog-list-id', name: 'Backlog' }, }, }, - }, + }), }; // backlog list not configured, so targetListId is undefined — won't match @@ -197,8 +170,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -210,7 +182,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { listAfter: { id: 'backlog-list-id', name: 'Backlog' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -224,8 +196,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -237,7 +208,7 @@ describe('TrelloStatusChangedBacklogTrigger', () => { listAfter: { id: 'backlog-list-id', name: 'Backlog' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); diff --git a/tests/unit/triggers/check-suite-failure.test.ts b/tests/unit/triggers/check-suite-failure.test.ts index 46ebac89..ec0452b7 100644 --- a/tests/unit/triggers/check-suite-failure.test.ts +++ b/tests/unit/triggers/check-suite-failure.test.ts @@ -1,29 +1,24 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + mockConfigResolverModule, + mockGitHubClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); + +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); + +vi.mock('../../../src/github/client.js', () => mockGitHubClientModule); + import { CheckSuiteFailureTrigger, resetFixAttempts, } from '../../../src/triggers/github/check-suite-failure.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; +import { createCheckSuitePayload, createMockProject } from '../../helpers/factories.js'; import { mockPersonaIdentities } from '../../helpers/mockPersonas.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); - -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); - -vi.mock('../../../src/github/client.js', () => ({ - githubClient: { - getPR: vi.fn(), - getCheckSuiteStatus: vi.fn(), - createPRComment: vi.fn(), - }, -})); - import { githubClient } from '../../../src/github/client.js'; vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ @@ -37,19 +32,17 @@ describe('CheckSuiteFailureTrigger', () => { const mockProject = createMockProject(); - const makeFailurePayload = (overrides: Record = {}) => ({ - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'failure', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - ...overrides, - }); + const makeFailurePayload = (overrides: Record = {}) => + createCheckSuitePayload({ + check_suite: { + id: 1, + status: 'completed', + conclusion: 'failure', + head_sha: 'sha123', + pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], + }, + ...overrides, + } as Parameters[0]); beforeEach(() => { resetFixAttempts(42); diff --git a/tests/unit/triggers/check-suite-success.test.ts b/tests/unit/triggers/check-suite-success.test.ts index 33fe9032..36ecf86b 100644 --- a/tests/unit/triggers/check-suite-success.test.ts +++ b/tests/unit/triggers/check-suite-success.test.ts @@ -1,10 +1,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { mockGitHubClientModule, mockTriggerCheckModule } from '../../helpers/sharedMocks.js'; +import { + mockConfigResolverModule, + mockGitHubClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); @@ -17,7 +18,7 @@ import { } from '../../../src/triggers/github/check-suite-success.js'; import { ReviewRequestedTrigger } from '../../../src/triggers/github/review-requested.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; +import { createCheckSuitePayload, createMockProject } from '../../helpers/factories.js'; import { mockPersonaIdentities } from '../../helpers/mockPersonas.js'; import { githubClient } from '../../../src/github/client.js'; @@ -35,19 +36,8 @@ describe('CheckSuiteSuccessTrigger', () => { const mockProject = createMockProject(); - const makeCheckSuitePayload = (overrides: Record = {}) => ({ - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - ...overrides, - }); + const makeCheckSuitePayload = (overrides: Record = {}) => + createCheckSuitePayload(overrides as Parameters[0]); beforeEach(() => { vi.mocked(lookupWorkItemForPR).mockResolvedValue('abc123'); diff --git a/tests/unit/triggers/github-pr-comment-mention.test.ts b/tests/unit/triggers/github-pr-comment-mention.test.ts index 4eda6373..9f268b42 100644 --- a/tests/unit/triggers/github-pr-comment-mention.test.ts +++ b/tests/unit/triggers/github-pr-comment-mention.test.ts @@ -6,14 +6,15 @@ const { mockGetPR, mockIsCascadeBot } = vi.hoisted(() => ({ mockIsCascadeBot: vi.fn(), })); -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); +import { + mockConfigResolverModule, + mockLogger, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); + +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); vi.mock('../../../src/github/client.js', () => ({ githubClient: { @@ -25,14 +26,7 @@ vi.mock('../../../src/github/personas.js', () => ({ isCascadeBot: mockIsCascadeBot, })); -vi.mock('../../../src/utils/logging.js', () => ({ - logger: { - warn: vi.fn(), - debug: vi.fn(), - info: vi.fn(), - error: vi.fn(), - }, -})); +vi.mock('../../../src/utils/logging.js', () => ({ logger: mockLogger })); vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ lookupWorkItemForPR: vi.fn(), diff --git a/tests/unit/triggers/jira-label-added.test.ts b/tests/unit/triggers/jira-label-added.test.ts index d1fd2786..289c7126 100644 --- a/tests/unit/triggers/jira-label-added.test.ts +++ b/tests/unit/triggers/jira-label-added.test.ts @@ -1,39 +1,23 @@ import { describe, expect, it, vi } from 'vitest'; +import { + mockAcknowledgmentsModule, + mockConfigProvider, + mockConfigResolverModule, + mockJiraClientModule, + mockReactionsModule, + mockTrelloClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); // Mocks required for PM integration registration (pm/index.js side-effect) -vi.mock('../../../src/config/provider.js', () => ({ - getIntegrationCredential: vi.fn(), - loadProjectConfigByBoardId: vi.fn(), - loadProjectConfigByJiraProjectKey: vi.fn(), - findProjectById: vi.fn(), -})); -vi.mock('../../../src/trello/client.js', () => ({ - withTrelloCredentials: vi.fn(), - trelloClient: { getCard: vi.fn() }, -})); -vi.mock('../../../src/jira/client.js', () => ({ - withJiraCredentials: vi.fn(), - jiraClient: {}, -})); -vi.mock('../../../src/router/acknowledgments.js', () => ({ - postTrelloAck: vi.fn(), - deleteTrelloAck: vi.fn(), - resolveTrelloBotMemberId: vi.fn(), - postJiraAck: vi.fn(), - deleteJiraAck: vi.fn(), - resolveJiraBotAccountId: vi.fn(), -})); -vi.mock('../../../src/router/reactions.js', () => ({ - sendAcknowledgeReaction: vi.fn(), -})); +vi.mock('../../../src/config/provider.js', () => mockConfigProvider); +vi.mock('../../../src/trello/client.js', () => mockTrelloClientModule); +vi.mock('../../../src/jira/client.js', () => mockJiraClientModule); +vi.mock('../../../src/router/acknowledgments.js', () => mockAcknowledgmentsModule); +vi.mock('../../../src/router/reactions.js', () => mockReactionsModule); // Register PM integrations in the registry import '../../../src/pm/index.js'; diff --git a/tests/unit/triggers/jira-status-changed.test.ts b/tests/unit/triggers/jira-status-changed.test.ts index 4c88aa02..9e7ab05b 100644 --- a/tests/unit/triggers/jira-status-changed.test.ts +++ b/tests/unit/triggers/jira-status-changed.test.ts @@ -1,21 +1,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + mockConfigResolverModule, + mockLogger, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; -vi.mock('../../../src/utils/logging.js', () => ({ - logger: { - warn: vi.fn(), - debug: vi.fn(), - info: vi.fn(), - error: vi.fn(), - }, -})); - -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); +vi.mock('../../../src/utils/logging.js', () => ({ logger: mockLogger })); + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); import { JiraStatusChangedTrigger } from '../../../src/triggers/jira/status-changed.js'; import { checkTriggerEnabled } from '../../../src/triggers/shared/trigger-check.js'; diff --git a/tests/unit/triggers/label-added.test.ts b/tests/unit/triggers/label-added.test.ts index 303919ef..a8e07b81 100644 --- a/tests/unit/triggers/label-added.test.ts +++ b/tests/unit/triggers/label-added.test.ts @@ -1,41 +1,25 @@ import { describe, expect, it, vi } from 'vitest'; -import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; +import { + mockAcknowledgmentsModule, + mockConfigProvider, + mockConfigResolverModule, + mockJiraClientModule, + mockReactionsModule, + mockTrelloClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); +import type { TriggerContext } from '../../../src/triggers/types.js'; // Mocks required for PM integration registration (pm/index.js side-effect) -vi.mock('../../../src/config/provider.js', () => ({ - getIntegrationCredential: vi.fn(), - loadProjectConfigByBoardId: vi.fn(), - loadProjectConfigByJiraProjectKey: vi.fn(), - findProjectById: vi.fn(), -})); -vi.mock('../../../src/trello/client.js', () => ({ - withTrelloCredentials: vi.fn(), - trelloClient: { getCard: vi.fn() }, -})); -vi.mock('../../../src/jira/client.js', () => ({ - withJiraCredentials: vi.fn(), - jiraClient: {}, -})); -vi.mock('../../../src/router/acknowledgments.js', () => ({ - postTrelloAck: vi.fn(), - deleteTrelloAck: vi.fn(), - resolveTrelloBotMemberId: vi.fn(), - postJiraAck: vi.fn(), - deleteJiraAck: vi.fn(), - resolveJiraBotAccountId: vi.fn(), -})); -vi.mock('../../../src/router/reactions.js', () => ({ - sendAcknowledgeReaction: vi.fn(), -})); +vi.mock('../../../src/config/provider.js', () => mockConfigProvider); +vi.mock('../../../src/trello/client.js', () => mockTrelloClientModule); +vi.mock('../../../src/jira/client.js', () => mockJiraClientModule); +vi.mock('../../../src/router/acknowledgments.js', () => mockAcknowledgmentsModule); +vi.mock('../../../src/router/reactions.js', () => mockReactionsModule); // Register PM integrations in the registry import '../../../src/pm/index.js'; @@ -43,6 +27,11 @@ import '../../../src/pm/index.js'; import { trelloClient } from '../../../src/trello/client.js'; import { checkTriggerEnabled } from '../../../src/triggers/shared/trigger-check.js'; import { ReadyToProcessLabelTrigger } from '../../../src/triggers/trello/label-added.js'; +import { + createMockProject, + createTrelloActionPayload, + createTrelloCard, +} from '../../helpers/factories.js'; describe('ReadyToProcessLabelTrigger', () => { const trigger = new ReadyToProcessLabelTrigger(); @@ -67,8 +56,7 @@ describe('ReadyToProcessLabelTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -79,7 +67,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(true); @@ -89,8 +77,7 @@ describe('ReadyToProcessLabelTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -101,7 +88,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'other-label-id', name: 'Other', color: 'red' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -111,8 +98,7 @@ describe('ReadyToProcessLabelTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -122,7 +108,7 @@ describe('ReadyToProcessLabelTrigger', () => { card: { id: 'card1', name: 'Test Card', idShort: 1, shortLink: 'abc' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -142,21 +128,14 @@ describe('ReadyToProcessLabelTrigger', () => { describe('handle', () => { it('should return null when trigger is disabled for the resolved agent', async () => { vi.mocked(checkTriggerEnabled).mockResolvedValueOnce(false); - mockGetCard.mockResolvedValue({ - id: 'card123', - name: 'Test Card', - desc: '', - url: 'https://trello.com/c/abc', - shortUrl: 'https://trello.com/c/abc', - idList: 'splitting-list-id', - labels: [], - }); + mockGetCard.mockResolvedValue( + createTrelloCard({ id: 'card123', idList: 'splitting-list-id' }), + ); const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -167,7 +146,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -181,21 +160,14 @@ describe('ReadyToProcessLabelTrigger', () => { }); it('returns splitting agent when card is in splitting list', async () => { - mockGetCard.mockResolvedValue({ - id: 'card123', - name: 'Test Card', - desc: '', - url: 'https://trello.com/c/abc', - shortUrl: 'https://trello.com/c/abc', - idList: 'splitting-list-id', - labels: [], - }); + mockGetCard.mockResolvedValue( + createTrelloCard({ id: 'card123', idList: 'splitting-list-id' }), + ); const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -206,7 +178,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -218,21 +190,20 @@ describe('ReadyToProcessLabelTrigger', () => { }); it('populates workItemUrl and workItemTitle from fetched card data', async () => { - mockGetCard.mockResolvedValue({ - id: 'card123', - name: 'My Feature Card', - desc: '', - url: 'https://trello.com/c/xyz123/my-feature-card', - shortUrl: 'https://trello.com/c/xyz123', - idList: 'splitting-list-id', - labels: [], - }); + mockGetCard.mockResolvedValue( + createTrelloCard({ + id: 'card123', + name: 'My Feature Card', + url: 'https://trello.com/c/xyz123/my-feature-card', + shortUrl: 'https://trello.com/c/xyz123', + idList: 'splitting-list-id', + }), + ); const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -243,7 +214,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -255,21 +226,14 @@ describe('ReadyToProcessLabelTrigger', () => { }); it('returns planning agent when card is in planning list', async () => { - mockGetCard.mockResolvedValue({ - id: 'card456', - name: 'Planning Card', - desc: '', - url: 'https://trello.com/c/def', - shortUrl: 'https://trello.com/c/def', - idList: 'planning-list-id', - labels: [], - }); + mockGetCard.mockResolvedValue( + createTrelloCard({ id: 'card456', name: 'Planning Card', idList: 'planning-list-id' }), + ); const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -280,7 +244,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -290,21 +254,14 @@ describe('ReadyToProcessLabelTrigger', () => { }); it('returns implementation agent when card is in todo list', async () => { - mockGetCard.mockResolvedValue({ - id: 'card789', - name: 'Todo Card', - desc: '', - url: 'https://trello.com/c/ghi', - shortUrl: 'https://trello.com/c/ghi', - idList: 'todo-list-id', - labels: [], - }); + mockGetCard.mockResolvedValue( + createTrelloCard({ id: 'card789', name: 'Todo Card', idList: 'todo-list-id' }), + ); const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -315,7 +272,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -325,21 +282,14 @@ describe('ReadyToProcessLabelTrigger', () => { }); it('returns null when card is in an unrecognized list (e.g. IN PROGRESS)', async () => { - mockGetCard.mockResolvedValue({ - id: 'card999', - name: 'Unknown List Card', - desc: '', - url: 'https://trello.com/c/xyz', - shortUrl: 'https://trello.com/c/xyz', - idList: 'in-progress-list-id', - labels: [], - }); + mockGetCard.mockResolvedValue( + createTrelloCard({ id: 'card999', idList: 'in-progress-list-id' }), + ); const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -350,7 +300,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -362,8 +312,7 @@ describe('ReadyToProcessLabelTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -373,7 +322,7 @@ describe('ReadyToProcessLabelTrigger', () => { label: { id: 'ready-label-id', name: 'Ready', color: 'green' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); diff --git a/tests/unit/triggers/pr-conflict-detected.test.ts b/tests/unit/triggers/pr-conflict-detected.test.ts index a1c3e70c..684b03c4 100644 --- a/tests/unit/triggers/pr-conflict-detected.test.ts +++ b/tests/unit/triggers/pr-conflict-detected.test.ts @@ -1,35 +1,30 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { - PRConflictDetectedTrigger, - resetConflictAttempts, -} from '../../../src/triggers/github/pr-conflict-detected.js'; -import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; -import { mockPersonaIdentities } from '../../helpers/mockPersonas.js'; + mockConfigResolverModule, + mockGitHubClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); - -vi.mock('../../../src/github/client.js', () => ({ - githubClient: { - getPR: vi.fn(), - createPRComment: vi.fn(), - }, -})); +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); -import { githubClient } from '../../../src/github/client.js'; +vi.mock('../../../src/github/client.js', () => mockGitHubClientModule); vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ lookupWorkItemForPR: vi.fn(), })); + import { lookupWorkItemForPR } from '../../../src/db/repositories/prWorkItemsRepository.js'; +import { githubClient } from '../../../src/github/client.js'; +import { + PRConflictDetectedTrigger, + resetConflictAttempts, +} from '../../../src/triggers/github/pr-conflict-detected.js'; import { checkTriggerEnabled } from '../../../src/triggers/shared/trigger-check.js'; +import type { TriggerContext } from '../../../src/triggers/types.js'; +import { createMockProject } from '../../helpers/factories.js'; +import { mockPersonaIdentities } from '../../helpers/mockPersonas.js'; describe('PRConflictDetectedTrigger', () => { const trigger = new PRConflictDetectedTrigger(); diff --git a/tests/unit/triggers/pr-merged.test.ts b/tests/unit/triggers/pr-merged.test.ts index 50d3e6f6..d448e04b 100644 --- a/tests/unit/triggers/pr-merged.test.ts +++ b/tests/unit/triggers/pr-merged.test.ts @@ -1,13 +1,18 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + mockAcknowledgmentsModule, + mockConfigProvider, + mockConfigResolverModule, + mockGitHubClientModule, + mockJiraClientModule, + mockReactionsModule, + mockTrelloClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); vi.mock('../../../src/triggers/shared/lifecycle-check.js', () => ({ isLifecycleTriggerEnabled: vi.fn().mockResolvedValue(true), @@ -18,11 +23,7 @@ vi.mock('../../../src/triggers/shared/backlog-check.js', () => ({ })); // Mock the GitHub client -vi.mock('../../../src/github/client.js', () => ({ - githubClient: { - getPR: vi.fn(), - }, -})); +vi.mock('../../../src/github/client.js', () => mockGitHubClientModule); // Mock the PM provider context const mockProvider = { @@ -35,31 +36,11 @@ vi.mock('../../../src/pm/context.js', () => ({ })); // Mocks required for PM integration registration (pm/index.js side-effect) -vi.mock('../../../src/config/provider.js', () => ({ - getIntegrationCredential: vi.fn(), - loadProjectConfigByBoardId: vi.fn(), - loadProjectConfigByJiraProjectKey: vi.fn(), - findProjectById: vi.fn(), -})); -vi.mock('../../../src/trello/client.js', () => ({ - withTrelloCredentials: vi.fn(), - trelloClient: { getCard: vi.fn() }, -})); -vi.mock('../../../src/jira/client.js', () => ({ - withJiraCredentials: vi.fn(), - jiraClient: {}, -})); -vi.mock('../../../src/router/acknowledgments.js', () => ({ - postTrelloAck: vi.fn(), - deleteTrelloAck: vi.fn(), - resolveTrelloBotMemberId: vi.fn(), - postJiraAck: vi.fn(), - deleteJiraAck: vi.fn(), - resolveJiraBotAccountId: vi.fn(), -})); -vi.mock('../../../src/router/reactions.js', () => ({ - sendAcknowledgeReaction: vi.fn(), -})); +vi.mock('../../../src/config/provider.js', () => mockConfigProvider); +vi.mock('../../../src/trello/client.js', () => mockTrelloClientModule); +vi.mock('../../../src/jira/client.js', () => mockJiraClientModule); +vi.mock('../../../src/router/acknowledgments.js', () => mockAcknowledgmentsModule); +vi.mock('../../../src/router/reactions.js', () => mockReactionsModule); vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ lookupWorkItemForPR: vi.fn(), })); diff --git a/tests/unit/triggers/pr-opened.test.ts b/tests/unit/triggers/pr-opened.test.ts index 4e38a259..ca6f2b82 100644 --- a/tests/unit/triggers/pr-opened.test.ts +++ b/tests/unit/triggers/pr-opened.test.ts @@ -1,18 +1,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { mockConfigResolverModule, mockTriggerCheckModule } from '../../helpers/sharedMocks.js'; + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); + +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); + import { PROpenedTrigger } from '../../../src/triggers/github/pr-opened.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; import { createMockProject } from '../../helpers/factories.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); - -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), - checkTriggerEnabledWithParams: vi.fn().mockResolvedValue({ enabled: true, parameters: {} }), -})); - vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ lookupWorkItemForPR: vi.fn(), })); diff --git a/tests/unit/triggers/pr-ready-to-merge.test.ts b/tests/unit/triggers/pr-ready-to-merge.test.ts index 98c556c9..ed973916 100644 --- a/tests/unit/triggers/pr-ready-to-merge.test.ts +++ b/tests/unit/triggers/pr-ready-to-merge.test.ts @@ -1,22 +1,21 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; - -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); +import { + mockAcknowledgmentsModule, + mockConfigProvider, + mockConfigResolverModule, + mockGitHubClientModule, + mockJiraClientModule, + mockReactionsModule, + mockTrelloClientModule, +} from '../../helpers/sharedMocks.js'; + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); vi.mock('../../../src/triggers/shared/lifecycle-check.js', () => ({ isLifecycleTriggerEnabled: vi.fn().mockResolvedValue(true), })); -vi.mock('../../../src/github/client.js', () => ({ - githubClient: { - getPR: vi.fn(), - getCheckSuiteStatus: vi.fn(), - getPRReviews: vi.fn(), - mergePR: vi.fn(), - }, -})); +vi.mock('../../../src/github/client.js', () => mockGitHubClientModule); // Mock the PM provider context const mockProvider = { @@ -29,31 +28,11 @@ vi.mock('../../../src/pm/context.js', () => ({ })); // Mocks required for PM integration registration (pm/index.js side-effect) -vi.mock('../../../src/config/provider.js', () => ({ - getIntegrationCredential: vi.fn(), - loadProjectConfigByBoardId: vi.fn(), - loadProjectConfigByJiraProjectKey: vi.fn(), - findProjectById: vi.fn(), -})); -vi.mock('../../../src/trello/client.js', () => ({ - withTrelloCredentials: vi.fn(), - trelloClient: { getCard: vi.fn() }, -})); -vi.mock('../../../src/jira/client.js', () => ({ - withJiraCredentials: vi.fn(), - jiraClient: {}, -})); -vi.mock('../../../src/router/acknowledgments.js', () => ({ - postTrelloAck: vi.fn(), - deleteTrelloAck: vi.fn(), - resolveTrelloBotMemberId: vi.fn(), - postJiraAck: vi.fn(), - deleteJiraAck: vi.fn(), - resolveJiraBotAccountId: vi.fn(), -})); -vi.mock('../../../src/router/reactions.js', () => ({ - sendAcknowledgeReaction: vi.fn(), -})); +vi.mock('../../../src/config/provider.js', () => mockConfigProvider); +vi.mock('../../../src/trello/client.js', () => mockTrelloClientModule); +vi.mock('../../../src/jira/client.js', () => mockJiraClientModule); +vi.mock('../../../src/router/acknowledgments.js', () => mockAcknowledgmentsModule); +vi.mock('../../../src/router/reactions.js', () => mockReactionsModule); vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ lookupWorkItemForPR: vi.fn(), })); @@ -63,7 +42,11 @@ import '../../../src/pm/index.js'; import { PRReadyToMergeTrigger } from '../../../src/triggers/github/pr-ready-to-merge.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; +import { + createCheckSuitePayload, + createMockProject, + createReviewPayload, +} from '../../helpers/factories.js'; import { lookupWorkItemForPR } from '../../../src/db/repositories/prWorkItemsRepository.js'; import { githubClient } from '../../../src/github/client.js'; @@ -94,18 +77,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; expect(trigger.matches(ctx)).toBe(true); @@ -115,8 +87,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'submitted', + payload: createReviewPayload({ review: { id: 100, state: 'approved', @@ -124,17 +95,8 @@ describe('PRReadyToMergeTrigger', () => { html_url: 'https://github.com/...', user: { login: 'reviewer' }, }, - pull_request: { - number: 42, - title: 'PR', - body: 'desc', - html_url: 'https://github.com/...', - head: { ref: 'feat', sha: 'abc' }, - base: { ref: 'main' }, - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, sender: { login: 'reviewer' }, - }, + }), }; expect(trigger.matches(ctx)).toBe(true); @@ -154,8 +116,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', + payload: createCheckSuitePayload({ check_suite: { id: 1, status: 'completed', @@ -163,9 +124,7 @@ describe('PRReadyToMergeTrigger', () => { head_sha: 'sha123', pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -175,8 +134,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', + payload: createCheckSuitePayload({ check_suite: { id: 1, status: 'completed', @@ -184,9 +142,7 @@ describe('PRReadyToMergeTrigger', () => { head_sha: 'sha123', pull_requests: [], }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -196,8 +152,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'submitted', + payload: createReviewPayload({ review: { id: 100, state: 'changes_requested', @@ -205,17 +160,8 @@ describe('PRReadyToMergeTrigger', () => { html_url: 'https://github.com/...', user: { login: 'reviewer' }, }, - pull_request: { - number: 42, - title: 'PR', - body: 'desc', - html_url: 'https://github.com/...', - head: { ref: 'feat', sha: 'abc' }, - base: { ref: 'main' }, - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, sender: { login: 'reviewer' }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -255,18 +201,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -319,18 +254,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -379,8 +303,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'submitted', + payload: createReviewPayload({ review: { id: 100, state: 'approved', @@ -388,17 +311,8 @@ describe('PRReadyToMergeTrigger', () => { html_url: 'https://github.com/...', user: { login: 'reviewer' }, }, - pull_request: { - number: 42, - title: 'Test PR', - body: 'https://trello.com/c/abc123/card-name', - html_url: 'https://github.com/...', - head: { ref: 'feature/test', sha: 'sha123' }, - base: { ref: 'main' }, - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, sender: { login: 'reviewer' }, - }, + }), }; const result = await trigger.handle(ctx); @@ -423,18 +337,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -466,18 +369,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -516,18 +408,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -573,18 +454,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -633,18 +503,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -703,18 +562,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: projectWithoutDone, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feat', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -786,18 +634,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: projectWithAutoLabel, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -830,18 +667,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: projectWithAutoLabel, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -873,18 +699,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: projectWithAutoLabel, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -926,18 +741,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: projectWithoutDone, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -977,18 +781,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: projectWithoutMerged, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); @@ -1032,18 +825,7 @@ describe('PRReadyToMergeTrigger', () => { const ctx: TriggerContext = { project: projectWithoutMergedOrDone, source: 'github', - payload: { - action: 'completed', - check_suite: { - id: 1, - status: 'completed', - conclusion: 'success', - head_sha: 'sha123', - pull_requests: [{ number: 42, head: { ref: 'feature/test', sha: 'sha123' } }], - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'github-actions' }, - }, + payload: createCheckSuitePayload(), }; const result = await trigger.handle(ctx); diff --git a/tests/unit/triggers/pr-review-submitted.test.ts b/tests/unit/triggers/pr-review-submitted.test.ts index 5d6a6cc0..d9749e64 100644 --- a/tests/unit/triggers/pr-review-submitted.test.ts +++ b/tests/unit/triggers/pr-review-submitted.test.ts @@ -1,18 +1,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { mockConfigResolverModule, mockTriggerCheckModule } from '../../helpers/sharedMocks.js'; + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); + +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); + import { PRReviewSubmittedTrigger } from '../../../src/triggers/github/pr-review-submitted.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; +import { createMockProject, createReviewPayload } from '../../helpers/factories.js'; import { mockPersonaIdentities } from '../../helpers/mockPersonas.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); - -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); - vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ lookupWorkItemForPR: vi.fn(), })); @@ -28,27 +25,8 @@ describe('PRReviewSubmittedTrigger', () => { const mockProject = createMockProject(); - const makeReviewPayload = (overrides: Record = {}) => ({ - action: 'submitted', - review: { - id: 100, - state: 'changes_requested', - body: 'Please fix the bug', - html_url: 'https://github.com/owner/repo/pull/42#pullrequestreview-100', - user: { login: 'cascade-reviewer' }, - }, - pull_request: { - number: 42, - title: 'Test PR', - body: 'https://trello.com/c/abc123/card-name', - html_url: 'https://github.com/owner/repo/pull/42', - head: { ref: 'feature/test', sha: 'abc' }, - base: { ref: 'main' }, - }, - repository: { full_name: 'owner/repo', html_url: 'https://github.com/owner/repo' }, - sender: { login: 'cascade-reviewer' }, - ...overrides, - }); + const makeReviewPayload = (overrides: Record = {}) => + createReviewPayload(overrides as Parameters[0]); describe('matches', () => { it('matches submitted review with changes_requested', () => { diff --git a/tests/unit/triggers/review-requested.test.ts b/tests/unit/triggers/review-requested.test.ts index 10c65aa4..66cf06b5 100644 --- a/tests/unit/triggers/review-requested.test.ts +++ b/tests/unit/triggers/review-requested.test.ts @@ -1,19 +1,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { mockConfigResolverModule, mockTriggerCheckModule } from '../../helpers/sharedMocks.js'; + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); + +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); + import { recentlyDispatched } from '../../../src/triggers/github/review-dispatch-dedup.js'; import { ReviewRequestedTrigger } from '../../../src/triggers/github/review-requested.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; import { createMockProject } from '../../helpers/factories.js'; import { mockPersonaIdentities } from '../../helpers/mockPersonas.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); - -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); - vi.mock('../../../src/db/repositories/prWorkItemsRepository.js', () => ({ lookupWorkItemForPR: vi.fn(), })); diff --git a/tests/unit/triggers/status-changed.test.ts b/tests/unit/triggers/status-changed.test.ts index 5b1946c2..5697ec27 100644 --- a/tests/unit/triggers/status-changed.test.ts +++ b/tests/unit/triggers/status-changed.test.ts @@ -1,52 +1,37 @@ import { describe, expect, it, vi } from 'vitest'; -import { mockLogger, mockTriggerCheckModule } from '../../helpers/sharedMocks.js'; +import { + mockAcknowledgmentsModule, + mockConfigProvider, + mockConfigResolverModule, + mockJiraClientModule, + mockLogger, + mockReactionsModule, + mockTrelloClientModule, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; vi.mock('../../../src/utils/logging.js', () => ({ logger: mockLogger })); -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); // Mocks required for PM integration registration (pm/index.js side-effect) -vi.mock('../../../src/config/provider.js', () => ({ - getIntegrationCredential: vi.fn(), - loadProjectConfigByBoardId: vi.fn(), - loadProjectConfigByJiraProjectKey: vi.fn(), - findProjectById: vi.fn(), -})); -vi.mock('../../../src/trello/client.js', () => ({ - withTrelloCredentials: vi.fn(), - trelloClient: { getCard: vi.fn() }, -})); -vi.mock('../../../src/jira/client.js', () => ({ - withJiraCredentials: vi.fn(), - jiraClient: {}, -})); -vi.mock('../../../src/router/acknowledgments.js', () => ({ - postTrelloAck: vi.fn(), - deleteTrelloAck: vi.fn(), - resolveTrelloBotMemberId: vi.fn(), - postJiraAck: vi.fn(), - deleteJiraAck: vi.fn(), - resolveJiraBotAccountId: vi.fn(), -})); -vi.mock('../../../src/router/reactions.js', () => ({ - sendAcknowledgeReaction: vi.fn(), -})); +vi.mock('../../../src/config/provider.js', () => mockConfigProvider); +vi.mock('../../../src/trello/client.js', () => mockTrelloClientModule); +vi.mock('../../../src/jira/client.js', () => mockJiraClientModule); +vi.mock('../../../src/router/acknowledgments.js', () => mockAcknowledgmentsModule); +vi.mock('../../../src/router/reactions.js', () => mockReactionsModule); // Register PM integrations in the registry import '../../../src/pm/index.js'; import { checkTriggerEnabled } from '../../../src/triggers/shared/trigger-check.js'; import { - TrelloStatusChangedPlanningTrigger, TrelloStatusChangedSplittingTrigger, TrelloStatusChangedTodoTrigger, } from '../../../src/triggers/trello/status-changed.js'; import type { TriggerContext } from '../../../src/triggers/types.js'; -import { createMockProject } from '../../helpers/factories.js'; +import { createMockProject, createTrelloActionPayload } from '../../helpers/factories.js'; describe('TrelloStatusChangedSplittingTrigger', () => { const trigger = TrelloStatusChangedSplittingTrigger; @@ -57,20 +42,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, - action: { - id: 'action1', - idMemberCreator: 'member1', - type: 'updateCard', - date: '2024-01-01', - data: { - card: { id: 'card1', name: 'Test Card', idShort: 1, shortLink: 'abc' }, - listBefore: { id: 'other-list', name: 'Other' }, - listAfter: { id: 'splitting-list-id', name: 'Splitting' }, - }, - }, - }, + payload: createTrelloActionPayload(), }; expect(trigger.matches(ctx)).toBe(true); @@ -80,8 +52,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -93,7 +64,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { listAfter: { id: 'splitting-list-id', name: 'Splitting' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -103,8 +74,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -115,7 +85,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { list: { id: 'splitting-list-id', name: 'Splitting' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(true); @@ -125,8 +95,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -137,7 +106,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { list: { id: 'other-list', name: 'Other' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(false); @@ -159,8 +128,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -172,7 +140,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { listAfter: { id: 'splitting-list-id', name: 'Splitting' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -189,8 +157,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -202,7 +169,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { listAfter: { id: 'splitting-list-id', name: 'Splitting' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -217,8 +184,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -230,7 +196,7 @@ describe('TrelloStatusChangedSplittingTrigger', () => { listAfter: { id: 'splitting-list-id', name: 'Splitting' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -251,8 +217,7 @@ describe('TrelloStatusChangedTodoTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -264,7 +229,7 @@ describe('TrelloStatusChangedTodoTrigger', () => { listAfter: { id: 'todo-list-id', name: 'TODO' }, }, }, - }, + }), }; expect(trigger.matches(ctx)).toBe(true); @@ -274,8 +239,7 @@ describe('TrelloStatusChangedTodoTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -287,7 +251,7 @@ describe('TrelloStatusChangedTodoTrigger', () => { listAfter: { id: 'todo-list-id', name: 'TODO' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); @@ -300,8 +264,7 @@ describe('TrelloStatusChangedTodoTrigger', () => { const ctx: TriggerContext = { project: mockProject, source: 'trello', - payload: { - model: { id: 'board123', name: 'Board' }, + payload: createTrelloActionPayload({ action: { id: 'action1', idMemberCreator: 'member1', @@ -313,7 +276,7 @@ describe('TrelloStatusChangedTodoTrigger', () => { listAfter: { id: 'todo-list-id', name: 'TODO' }, }, }, - }, + }), }; const result = await trigger.handle(ctx); diff --git a/tests/unit/triggers/trello-comment-mention.test.ts b/tests/unit/triggers/trello-comment-mention.test.ts index 63e1d433..985fe708 100644 --- a/tests/unit/triggers/trello-comment-mention.test.ts +++ b/tests/unit/triggers/trello-comment-mention.test.ts @@ -13,22 +13,16 @@ vi.mock('../../../src/trello/client.js', () => ({ }, })); -vi.mock('../../../src/utils/logging.js', () => ({ - logger: { - warn: vi.fn(), - debug: vi.fn(), - info: vi.fn(), - error: vi.fn(), - }, -})); +import { + mockConfigResolverModule, + mockLogger, + mockTriggerCheckModule, +} from '../../helpers/sharedMocks.js'; -vi.mock('../../../src/triggers/config-resolver.js', () => ({ - isTriggerEnabled: vi.fn().mockResolvedValue(true), - getTriggerParameters: vi.fn().mockResolvedValue({}), -})); -vi.mock('../../../src/triggers/shared/trigger-check.js', () => ({ - checkTriggerEnabled: vi.fn().mockResolvedValue(true), -})); +vi.mock('../../../src/utils/logging.js', () => ({ logger: mockLogger })); + +vi.mock('../../../src/triggers/config-resolver.js', () => mockConfigResolverModule); +vi.mock('../../../src/triggers/shared/trigger-check.js', () => mockTriggerCheckModule); // We need to reset the module-level cache between tests. // The module uses a module-level variable `cachedMemberInfo`.