feat: Step Function + hardning
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
|
||||
import {fireEvent, render, screen, waitFor} 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(<QueryClientProvider client={client}><WorkflowExecutionDebugger resource={machine} runtimeReachable={true}/></QueryClientProvider>)
|
||||
}
|
||||
|
||||
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((_, element) => element?.tagName === 'SUMMARY' && element.textContent?.includes('ExecutionStarted') === true)).toBeInTheDocument()
|
||||
expect(await screen.findByText((_, element) => element?.tagName === 'SUMMARY' && element.textContent?.includes('ExecutionSucceeded') === true)).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((_, element) => element?.tagName === 'SUMMARY' && element.textContent?.includes('ExecutionStarted') === true)).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'}))
|
||||
await waitFor(() => expect(startWorkflowExecution).toHaveBeenCalledWith(machine.id, {input: '{"orderId":"1"}'}))
|
||||
})
|
||||
|
||||
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((_, element) => element?.tagName === 'SUMMARY' && element.textContent?.includes('ExecutionStarted') === true)).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((_, element) => element?.tagName === 'SUMMARY' && element.textContent?.includes('TaskStateEntered') === true)).toBeInTheDocument()
|
||||
|
||||
expect(await screen.findByText((_, element) => element?.tagName === 'SUMMARY' && element.textContent?.includes('TaskSucceeded') === true, {}, {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((_, element) => element?.tagName === 'SUMMARY' && element.textContent?.includes('Event') === true)).toHaveLength(10)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user