Implement independent error storage system and comprehensive improvements

Major Features:
• Add persistent error tracking for timeout/network failures that bypasses PayloadCMS output limitations
• Implement smart error classification (timeout, DNS, connection, network) with duration-based detection
• Add comprehensive test infrastructure with MongoDB in-memory testing and enhanced mocking
• Fix HTTP request handler error preservation with detailed context storage
• Add independent execution tracking with success/failure status and duration metrics

Technical Improvements:
• Update JSONPath documentation to use correct $.trigger.doc syntax across all step types
• Fix PayloadCMS job execution to use runByID instead of run() for reliable task processing
• Add enhanced HTTP error handling that preserves outputs for 4xx/5xx status codes
• Implement proper nock configuration with undici for Node.js 22 fetch interception
• Add comprehensive unit tests for WorkflowExecutor with mocked PayloadCMS instances

Developer Experience:
• Add detailed error information in workflow context with URL, method, timeout, attempts
• Update README with HTTP error handling patterns and enhanced error tracking examples
• Add test helpers and setup infrastructure for reliable integration testing
• Fix workflow step validation and JSONPath field descriptions

Breaking Changes: None - fully backward compatible

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-04 18:03:30 +02:00
parent 04100787d7
commit 74217d532d
26 changed files with 2472 additions and 565 deletions

View File

@@ -1,91 +1,50 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import type { Payload } from 'payload'
import { getPayload } from 'payload'
import config from './payload.config'
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { getTestPayload, cleanDatabase } from './test-setup.js'
import { mockHttpBin, testFixtures } from './test-helpers.js'
describe('Workflow Trigger Test', () => {
let payload: Payload
beforeAll(async () => {
payload = await getPayload({ config: await config })
}, 60000)
afterAll(async () => {
if (!payload) return
try {
// Clear test data
const workflows = await payload.find({
collection: 'workflows',
limit: 100
})
for (const workflow of workflows.docs) {
await payload.delete({
collection: 'workflows',
id: workflow.id
})
}
const runs = await payload.find({
collection: 'workflow-runs',
limit: 100
})
for (const run of runs.docs) {
await payload.delete({
collection: 'workflow-runs',
id: run.id
})
}
const posts = await payload.find({
collection: 'posts',
limit: 100
})
for (const post of posts.docs) {
await payload.delete({
collection: 'posts',
id: post.id
})
}
} catch (error) {
console.warn('Cleanup failed:', error)
beforeEach(async () => {
await cleanDatabase()
// Set up HTTP mocks
const expectedRequestData = {
message: 'Post created',
postId: expect.any(String), // MongoDB ObjectId
postTitle: 'Test post content for workflow trigger'
}
}, 30000)
mockHttpBin.mockPost(expectedRequestData)
})
afterEach(async () => {
await cleanDatabase()
mockHttpBin.cleanup()
})
it('should create a workflow run when a post is created', async () => {
const payload = getTestPayload()
// Use test fixtures for consistent data
const testWorkflow = {
...testFixtures.basicWorkflow,
name: 'Test Post Creation Workflow',
description: 'Triggers when a post is created',
steps: [
{
...testFixtures.httpRequestStep(),
name: 'log-post',
body: {
message: 'Post created',
postId: '$.trigger.doc.id',
postTitle: '$.trigger.doc.content'
}
}
]
}
// Create a workflow with collection trigger
const workflow = await payload.create({
collection: 'workflows',
data: {
name: 'Test Post Creation Workflow',
description: 'Triggers when a post is created',
triggers: [
{
type: 'collection-trigger',
collectionSlug: 'posts',
operation: 'create'
}
],
steps: [
{
name: 'log-post',
step: 'http-request-step',
url: 'https://httpbin.org/post',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: {
message: 'Post created',
postId: '$.trigger.doc.id',
postTitle: '$.trigger.doc.content'
}
}
]
}
data: testWorkflow
})
expect(workflow).toBeDefined()
@@ -94,9 +53,7 @@ describe('Workflow Trigger Test', () => {
// Create a post to trigger the workflow
const post = await payload.create({
collection: 'posts',
data: {
content: 'This should trigger the workflow'
}
data: testFixtures.testPost
})
expect(post).toBeDefined()
@@ -117,7 +74,14 @@ describe('Workflow Trigger Test', () => {
})
expect(runs.totalDocs).toBeGreaterThan(0)
expect(runs.docs[0].workflow).toBe(typeof workflow.id === 'object' ? workflow.id.toString() : workflow.id)
// Check if workflow is an object or ID
const workflowRef = runs.docs[0].workflow
const workflowId = typeof workflowRef === 'object' && workflowRef !== null
? (workflowRef as any).id
: workflowRef
expect(workflowId).toBe(workflow.id) // Should reference the workflow ID
console.log('✅ Workflow run created successfully!')
console.log(`Run status: ${runs.docs[0].status}`)