diff --git a/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.spec.ts b/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.spec.ts index 1a8a12bef..a39980e8d 100644 --- a/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.spec.ts +++ b/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.spec.ts @@ -1,6 +1,9 @@ import { TaskFrequency } from '@trycompai/db'; +import { db } from '@db'; +import { getManifest } from '@trycompai/integration-platform'; import { filterDueTasks, + integrationChecksSchedule, resolveProviderChecks, } from './run-integration-checks-schedule'; @@ -13,6 +16,7 @@ jest.mock('@db', () => ({ integrationConnection: { findMany: jest.fn() }, task: { findMany: jest.fn(), update: jest.fn() }, dynamicIntegration: { findMany: jest.fn() }, + organization: { findMany: jest.fn() }, }, TaskFrequency: { daily: 'daily', @@ -21,6 +25,10 @@ jest.mock('@db', () => ({ quarterly: 'quarterly', yearly: 'yearly', }, + TaskAutomationStatus: { + AUTOMATED: 'AUTOMATED', + MANUAL: 'MANUAL', + }, })); jest.mock('@trycompai/integration-platform', () => ({ @@ -172,3 +180,59 @@ describe('resolveProviderChecks (static vs dynamic)', () => { expect(checks).toEqual([{ id: 'c1', taskMapping: null }]); }); }); + +describe('orchestrator excludes MANUAL tasks from scheduled runs', () => { + // Cast the db/getManifest mocks to their jest.Mock shape for setup. + const taskFindMany = (db as unknown as { task: { findMany: jest.Mock } }).task + .findMany; + const connectionFindMany = ( + db as unknown as { integrationConnection: { findMany: jest.Mock } } + ).integrationConnection.findMany; + const dynamicFindMany = ( + db as unknown as { dynamicIntegration: { findMany: jest.Mock } } + ).dynamicIntegration.findMany; + const orgFindMany = ( + db as unknown as { organization: { findMany: jest.Mock } } + ).organization.findMany; + const getManifestMock = getManifest as jest.Mock; + + // schedules.task is mocked to return the config, so .run is invokable here. + const runOrchestrator = ( + integrationChecksSchedule as unknown as { + run: (p: { timestamp: Date; lastTimestamp?: Date }) => Promise; + } + ).run; + + beforeEach(() => { + jest.clearAllMocks(); + connectionFindMany.mockResolvedValue([ + { + id: 'conn1', + provider: { slug: 'github' }, + organizationId: 'org1', + organization: { id: 'org1', name: 'Org' }, + metadata: null, + }, + ]); + dynamicFindMany.mockResolvedValue([]); + getManifestMock.mockReturnValue({ + checks: [{ id: 'c1', taskMapping: 'tpl_a' }], + }); + taskFindMany.mockResolvedValue([]); // no due tasks → no triggers + orgFindMany.mockResolvedValue([]); // device-sync section is a no-op + }); + + it('queries candidate tasks with an automationStatus != MANUAL filter', async () => { + await runOrchestrator({ timestamp: new Date(), lastTimestamp: new Date() }); + + expect(taskFindMany).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ + organizationId: 'org1', + taskTemplateId: { in: ['tpl_a'] }, + automationStatus: { not: 'MANUAL' }, + }), + }), + ); + }); +}); diff --git a/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts b/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts index 5819bde16..f3e7a91dc 100644 --- a/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts +++ b/apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts @@ -1,5 +1,5 @@ import { getManifest } from '@trycompai/integration-platform'; -import { db, TaskFrequency } from '@db'; +import { db, TaskAutomationStatus, TaskFrequency } from '@db'; import { logger, schedules } from '@trigger.dev/sdk'; import { runTaskIntegrationChecks } from './run-task-integration-checks'; import { runDeviceSync } from './run-device-sync'; @@ -149,11 +149,15 @@ export const integrationChecksSchedule = schedules.task({ continue; } - // Find tasks in this org that match these templates + // Find tasks in this org that match these templates. MANUAL tasks are + // excluded: the scheduler must not auto-run checks (or flip the status) on + // a task the customer manages manually — mirrors the UI, which hides the + // automation/checks tab when automationStatus is MANUAL. const candidateTasks = await db.task.findMany({ where: { organizationId: connection.organizationId, taskTemplateId: { in: taskTemplateIds as string[] }, + automationStatus: { not: TaskAutomationStatus.MANUAL }, }, select: { id: true,