import {QueryClient, QueryClientProvider} from '@tanstack/react-query' import {fireEvent, render, screen, waitFor, within} from '@testing-library/react' import userEvent from '@testing-library/user-event' import {afterEach, describe, expect, test, vi} from 'vitest' import {WorkflowExecutionDebugger} from '@/components/WorkflowExecutionDebugger' import {describeWorkflowExecution, getWorkflowExecutionHistory, listWorkflowExecutions, startWorkflowExecution, stopWorkflowExecution} from '@/api/cloudProxyClient' import type {CloudResource} from '@/types/resource' vi.mock('@/api/cloudProxyClient', () => ({ describeWorkflowExecution: vi.fn(), getWorkflowExecutionHistory: vi.fn(), listWorkflowExecutions: vi.fn(), startWorkflowExecution: vi.fn(), stopWorkflowExecution: vi.fn(), })) const machine: CloudResource = {id: 'arn:aws:states:us-east-1:000000000000:stateMachine:orders', name: 'orders', cloud: 'aws', service: 'workflows', type: 'state-machine', region: 'us-east-1', createdAt: null, metadata: {machineType: 'STANDARD'}} const executionArn = 'arn:aws:states:us-east-1:000000000000:execution:orders:run-1' function renderPanel() { const client = new QueryClient({defaultOptions: {queries: {retry: false}}}) return render() } afterEach(() => { vi.clearAllMocks() vi.useRealTimers() }) describe('WorkflowExecutionDebugger', () => { test('starts an execution and automatically aggregates paged history in backend order', async () => { vi.mocked(listWorkflowExecutions).mockResolvedValue([{executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z'}]) vi.mocked(startWorkflowExecution).mockResolvedValue({executionArn, startDate: '2026-07-28T10:01:00.000Z'}) vi.mocked(describeWorkflowExecution).mockResolvedValue({executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z', stateMachineArn: machine.id, input: '{}', output: '{"ok":true}'}) vi.mocked(getWorkflowExecutionHistory).mockResolvedValueOnce({events: [{id: 1, type: 'ExecutionStarted', timestamp: '2026-07-28T10:01:00.000Z'}], nextToken: 'next'}).mockResolvedValueOnce({events: [{id: 1, type: 'ExecutionStarted', timestamp: '2026-07-28T10:01:00.000Z'}, {id: 2, type: 'ExecutionSucceeded'}]}) const user = userEvent.setup() renderPanel() await user.click(await screen.findByText('run-1')) expect(await screen.findByText('ExecutionStarted')).toBeInTheDocument() expect(await screen.findByText('ExecutionSucceeded')).toBeInTheDocument() expect(getWorkflowExecutionHistory).toHaveBeenNthCalledWith(1, machine.id, executionArn, undefined, expect.any(AbortSignal)) expect(getWorkflowExecutionHistory).toHaveBeenNthCalledWith(2, machine.id, executionArn, 'next', expect.any(AbortSignal)) expect(screen.getAllByText('ExecutionStarted')).toHaveLength(1) expect(screen.queryByRole('button', {name: 'Load more history'})).not.toBeInTheDocument() fireEvent.change(screen.getByLabelText('JSON input'), {target: {value: '{"orderId":"1"}'}}) await user.click(screen.getByRole('button', {name: 'Start execution'})) expect(screen.getByRole('alertdialog')).toHaveTextContent('{"orderId":"1"}') await user.click(within(screen.getByRole('alertdialog')).getByRole('button', {name: 'Start execution'})) await waitFor(() => expect(startWorkflowExecution).toHaveBeenCalledWith(machine.id, {input: '{"orderId":"1"}'})) }) test('does not start until the inline confirmation is accepted', async () => { vi.mocked(listWorkflowExecutions).mockResolvedValue([]) const user = userEvent.setup() renderPanel() await user.click(screen.getByRole('button', {name: 'Start execution'})) expect(screen.getByRole('alertdialog')).toHaveTextContent('may invoke an unavailable Lambda') expect(startWorkflowExecution).not.toHaveBeenCalled() await user.click(screen.getByRole('button', {name: 'Cancel'})) expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() expect(startWorkflowExecution).not.toHaveBeenCalled() }) test('confirms stopping only a running execution', async () => { vi.stubGlobal('confirm', vi.fn(() => true)) vi.mocked(listWorkflowExecutions).mockResolvedValue([{executionArn, name: 'run-1', status: 'RUNNING', startDate: '2026-07-28T10:01:00.000Z'}]) vi.mocked(describeWorkflowExecution).mockResolvedValue({executionArn, name: 'run-1', status: 'RUNNING', startDate: '2026-07-28T10:01:00.000Z', stateMachineArn: machine.id}) vi.mocked(getWorkflowExecutionHistory).mockResolvedValue({events: []}) vi.mocked(stopWorkflowExecution).mockResolvedValue() const user = userEvent.setup() renderPanel() await user.click(await screen.findByText('run-1')) await user.click(await screen.findByRole('button', {name: 'Stop execution'})) await waitFor(() => expect(stopWorkflowExecution).toHaveBeenCalledWith(machine.id, executionArn)) }) test('keeps loaded events visible when a later history page cannot load', async () => { vi.mocked(listWorkflowExecutions).mockResolvedValue([{executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z'}]) vi.mocked(describeWorkflowExecution).mockResolvedValue({executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z', stateMachineArn: machine.id}) vi.mocked(getWorkflowExecutionHistory).mockResolvedValueOnce({events: [{id: 1, type: 'ExecutionStarted'}], nextToken: 'next'}).mockRejectedValueOnce(new Error('Unable to load the next history page')) const user = userEvent.setup() renderPanel() await user.click(await screen.findByText('run-1')) expect(await screen.findByText('Unable to load the next history page')).toBeInTheDocument() expect(screen.getByText('ExecutionStarted')).toBeInTheDocument() }) test('polls only the trailing history page for a running execution and appends new events', async () => { vi.mocked(listWorkflowExecutions).mockResolvedValue([{executionArn, name: 'run-1', status: 'RUNNING', startDate: '2026-07-28T10:01:00.000Z'}]) vi.mocked(describeWorkflowExecution).mockResolvedValue({executionArn, name: 'run-1', status: 'RUNNING', startDate: '2026-07-28T10:01:00.000Z', stateMachineArn: machine.id}) vi.mocked(getWorkflowExecutionHistory) .mockResolvedValueOnce({events: [{id: 1, type: 'ExecutionStarted'}], nextToken: 'page-2'}) .mockResolvedValueOnce({events: [{id: 2, type: 'TaskStateEntered'}]}) .mockResolvedValueOnce({events: [{id: 2, type: 'TaskStateEntered'}, {id: 3, type: 'TaskSucceeded'}]}) const user = userEvent.setup() renderPanel() await user.click(await screen.findByText('run-1')) expect(await screen.findByText('TaskStateEntered')).toBeInTheDocument() expect(await screen.findByText('TaskSucceeded', {}, {timeout: 3_000})).toBeInTheDocument() expect(getWorkflowExecutionHistory).toHaveBeenNthCalledWith(3, machine.id, executionArn, 'page-2', expect.any(AbortSignal)) expect(getWorkflowExecutionHistory).toHaveBeenCalledTimes(3) }) test('marks history incomplete at the explicit page ceiling while retaining loaded progress', async () => { vi.mocked(listWorkflowExecutions).mockResolvedValue([{executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z'}]) vi.mocked(describeWorkflowExecution).mockResolvedValue({executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z', stateMachineArn: machine.id}) let page = 0 vi.mocked(getWorkflowExecutionHistory).mockImplementation(async () => { page++ return {events: [{id: page, type: 'Event'}], nextToken: 'more'} }) const user = userEvent.setup() renderPanel() await user.click(await screen.findByText('run-1')) expect(await screen.findByText('History incomplete: reached the 10-page limit.')).toBeInTheDocument() expect(getWorkflowExecutionHistory).toHaveBeenCalledTimes(10) expect(screen.getAllByText('Event')).toHaveLength(10) }) test('renders state status, selected state input, definition, and state events', async () => { const workflow: CloudResource = {...machine, metadata: {machineType: 'STANDARD', definition: JSON.stringify({StartAt: 'ChargePayment', States: {ChargePayment: {Type: 'Task', Resource: 'arn:aws:states:::lambda:invoke', Parameters: {FunctionName: 'arn:aws:lambda:us-east-1:000000000000:function:charge-payment:$LATEST'}}, PaymentFailed: {Type: 'Task', Resource: 'arn:aws:states:::sns:publish', End: true}}})}} vi.mocked(listWorkflowExecutions).mockResolvedValue([{executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z'}]) vi.mocked(describeWorkflowExecution).mockResolvedValue({executionArn, name: 'run-1', status: 'SUCCEEDED', startDate: '2026-07-28T10:01:00.000Z', stateMachineArn: machine.id, input: '{"orderId":"1"}'}) vi.mocked(getWorkflowExecutionHistory).mockResolvedValue({events: [ {id: 1, type: 'TaskStateEntered', timestamp: '2026-07-28T10:01:00.000Z', stateEnteredEventDetails: {name: 'ChargePayment', input: '{"orderId":"1"}'}}, {id: 2, previousEventId: 1, type: 'TaskFailed', taskFailedEventDetails: {error: 'Exception'}}, {id: 3, previousEventId: 2, type: 'TaskStateExited', stateExitedEventDetails: {name: 'ChargePayment'}}, {id: 4, type: 'TaskStateEntered', timestamp: '2026-07-28T10:01:01.000Z', stateEnteredEventDetails: {name: 'PaymentFailed', input: '{"orderId":"1"}'}}, {id: 5, previousEventId: 4, type: 'TaskSucceeded', taskSucceededEventDetails: {output: '{"messageId":"1"}'}}, {id: 6, previousEventId: 5, type: 'TaskStateExited', stateExitedEventDetails: {name: 'PaymentFailed', output: '{"messageId":"1"}'}}, ]}) const client = new QueryClient({defaultOptions: {queries: {retry: false}}}) const user = userEvent.setup() render() await user.click(await screen.findByText('run-1')) expect(await screen.findByText('Caught error')).toBeInTheDocument() await user.click(screen.getAllByRole('button', {name: /ChargePayment/})[0]) expect(screen.getByRole('link', {name: 'Open logs'})).toHaveAttribute('href', '/cloud-explorer/aws/logs?resource=%2Faws%2Flambda%2Fcharge-payment') expect(screen.getByText('{"orderId":"1"}')).toBeInTheDocument() await user.click(screen.getByRole('tab', {name: 'definition'})) expect(screen.getByText((_, element) => element?.tagName === 'PRE' && element.textContent?.includes('arn:aws:states:::lambda:invoke') === true)).toBeInTheDocument() await user.click(screen.getByRole('tab', {name: 'events'})) expect(screen.getAllByText('TaskFailed')).toHaveLength(2) }) })