|
1 | 1 | /** |
2 | 2 | * @vitest-environment node |
3 | 3 | */ |
4 | | -import { describe, expect, it } from 'vitest' |
5 | | -import { updateResumeOutputInAggregationBuffers } from '@/lib/workflows/executor/human-in-the-loop-manager' |
| 4 | +import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' |
| 5 | +import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 6 | + |
| 7 | +vi.mock('@sim/db', () => dbChainMock) |
| 8 | + |
| 9 | +import { |
| 10 | + PauseResumeManager, |
| 11 | + updateResumeOutputInAggregationBuffers, |
| 12 | +} from '@/lib/workflows/executor/human-in-the-loop-manager' |
6 | 13 | import type { SerializableExecutionState } from '@/executor/execution/types' |
| 14 | +import type { PausePoint, SerializedSnapshot } from '@/executor/types' |
7 | 15 |
|
8 | 16 | function createExecutionState(): SerializableExecutionState { |
9 | 17 | return { |
@@ -143,3 +151,68 @@ describe('updateResumeOutputInAggregationBuffers', () => { |
143 | 151 | }) |
144 | 152 | }) |
145 | 153 | }) |
| 154 | + |
| 155 | +describe('PauseResumeManager.persistPauseResult metadata merge on re-pause', () => { |
| 156 | + beforeEach(() => { |
| 157 | + vi.clearAllMocks() |
| 158 | + resetDbChainMock() |
| 159 | + }) |
| 160 | + |
| 161 | + it('preserves the stashed cellContext when an existing paused row re-pauses (chained waits)', async () => { |
| 162 | + const cellContext = { |
| 163 | + tableId: 'table-1', |
| 164 | + rowId: 'row-1', |
| 165 | + workspaceId: 'workspace-1', |
| 166 | + groupId: 'group-1', |
| 167 | + workflowId: 'workflow-1', |
| 168 | + } |
| 169 | + const existingRow = { |
| 170 | + id: 'paused-exec-1', |
| 171 | + workflowId: 'workflow-1', |
| 172 | + executionId: 'execution-1', |
| 173 | + status: 'partially_resumed', |
| 174 | + pausePoints: { |
| 175 | + 'ctx-wait-1': { contextId: 'ctx-wait-1', blockId: 'wait1', resumeStatus: 'resuming' }, |
| 176 | + }, |
| 177 | + metadata: { |
| 178 | + pauseScope: 'execution', |
| 179 | + triggerIds: ['start'], |
| 180 | + executorUserId: 'user-1', |
| 181 | + cellContext, |
| 182 | + }, |
| 183 | + } |
| 184 | + |
| 185 | + // First `.limit(1)` resolves the select-for-update to the existing row, |
| 186 | + // forcing persistPauseResult down the update (not insert) branch. |
| 187 | + dbChainMockFns.limit.mockResolvedValueOnce([existingRow]) |
| 188 | + |
| 189 | + const snapshotSeed: SerializedSnapshot = { snapshot: '{}', triggerIds: [] } |
| 190 | + const pausePoints: PausePoint[] = [ |
| 191 | + { |
| 192 | + contextId: 'ctx-wait-2', |
| 193 | + blockId: 'wait2', |
| 194 | + pauseKind: 'time', |
| 195 | + resumeAt: new Date(Date.now() + 60_000).toISOString(), |
| 196 | + resumeStatus: 'paused', |
| 197 | + } as PausePoint, |
| 198 | + ] |
| 199 | + |
| 200 | + await PauseResumeManager.persistPauseResult({ |
| 201 | + workflowId: 'workflow-1', |
| 202 | + executionId: 'execution-1', |
| 203 | + pausePoints, |
| 204 | + snapshotSeed, |
| 205 | + executorUserId: 'user-1', |
| 206 | + }) |
| 207 | + |
| 208 | + const updateSetCall = dbChainMockFns.set.mock.calls.find( |
| 209 | + ([arg]) => arg && typeof arg === 'object' && 'metadata' in (arg as Record<string, unknown>) |
| 210 | + ) |
| 211 | + expect(updateSetCall).toBeDefined() |
| 212 | + |
| 213 | + const updatedMetadata = (updateSetCall![0] as { metadata: Record<string, unknown> }).metadata |
| 214 | + expect(updatedMetadata.cellContext).toEqual(cellContext) |
| 215 | + expect(updatedMetadata.pauseScope).toBe('execution') |
| 216 | + expect(updatedMetadata.executorUserId).toBe('user-1') |
| 217 | + }) |
| 218 | +}) |
0 commit comments