chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { BlockState, WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
vi.mock('@/stores/workflows/workflow/store', () => ({
|
||||
useWorkflowStore: {
|
||||
getState: () => ({
|
||||
getWorkflowState: () => ({ blocks: {}, edges: [], loops: {}, parallels: {} }),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workflows/utils', () => ({
|
||||
mergeSubblockState: (blocks: Record<string, BlockState>) => blocks,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/workflows/sanitization/key-validation', () => ({
|
||||
isValidKey: (key: string) => key !== 'undefined' && key !== 'null' && key !== '',
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/workflows/autolayout', () => ({
|
||||
transferBlockHeights: vi.fn(),
|
||||
applyTargetedLayout: (blocks: Record<string, BlockState>) => blocks,
|
||||
getTargetedLayoutImpact: () => ({
|
||||
layoutBlockIds: [],
|
||||
shiftSourceBlockIds: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/workflows/autolayout/constants', () => ({
|
||||
DEFAULT_HORIZONTAL_SPACING: 500,
|
||||
DEFAULT_VERTICAL_SPACING: 400,
|
||||
DEFAULT_LAYOUT_OPTIONS: {},
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workflows/workflow/utils', () => ({
|
||||
generateLoopBlocks: () => ({}),
|
||||
generateParallelBlocks: () => ({}),
|
||||
}))
|
||||
|
||||
vi.mock('@/blocks', () => ({
|
||||
getBlock: () => null,
|
||||
getAllBlocks: () => ({}),
|
||||
getAllBlockTypes: () => [],
|
||||
getBlockByToolName: () => null,
|
||||
getBlocksByCategory: () => [],
|
||||
isValidBlockType: () => false,
|
||||
registry: {},
|
||||
}))
|
||||
|
||||
vi.mock('@/tools/utils', () => ({
|
||||
getTool: () => null,
|
||||
}))
|
||||
|
||||
vi.mock('@/triggers', () => ({
|
||||
getTrigger: () => null,
|
||||
isTriggerValid: () => false,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/workflows/blocks/block-outputs', () => ({
|
||||
getEffectiveBlockOutputs: () => ({}),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/workflows/subblocks/visibility', () => ({
|
||||
buildDefaultCanonicalModes: () => ({}),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/workflows/triggers/triggers', () => ({
|
||||
TRIGGER_TYPES: {},
|
||||
classifyStartBlockType: () => null,
|
||||
StartBlockPath: {},
|
||||
getTriggerOutputs: () => ({}),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-trigger-config-aggregation', () => ({
|
||||
populateTriggerFieldsFromConfig: () => [],
|
||||
}))
|
||||
|
||||
vi.mock('@/executor/constants', () => ({
|
||||
isAnnotationOnlyBlock: () => false,
|
||||
BLOCK_DIMENSIONS: { MIN_HEIGHT: 100 },
|
||||
HANDLE_POSITIONS: {},
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workflows/registry/store', () => ({
|
||||
useWorkflowRegistry: {
|
||||
getState: () => ({
|
||||
activeWorkflowId: null,
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workflows/subblock/store', () => ({
|
||||
useSubBlockStore: {
|
||||
getState: () => ({
|
||||
workflowValues: {},
|
||||
getValue: () => null,
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
import { WorkflowDiffEngine } from './diff-engine'
|
||||
|
||||
function createMockBlock(overrides: Partial<BlockState> = {}): BlockState {
|
||||
return {
|
||||
id: 'block-1',
|
||||
type: 'agent',
|
||||
name: 'Test Block',
|
||||
enabled: true,
|
||||
position: { x: 0, y: 0 },
|
||||
subBlocks: {},
|
||||
outputs: {},
|
||||
...overrides,
|
||||
} as BlockState
|
||||
}
|
||||
|
||||
function createMockWorkflowState(blocks: Record<string, BlockState>): WorkflowState {
|
||||
return {
|
||||
blocks,
|
||||
edges: [],
|
||||
loops: {},
|
||||
parallels: {},
|
||||
}
|
||||
}
|
||||
|
||||
describe('WorkflowDiffEngine', () => {
|
||||
let engine: WorkflowDiffEngine
|
||||
|
||||
beforeEach(() => {
|
||||
engine = new WorkflowDiffEngine()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('hasBlockChanged detection', () => {
|
||||
describe('locked state changes', () => {
|
||||
it.concurrent(
|
||||
'should NOT detect a diff when only the locked state changes (false -> true)',
|
||||
async () => {
|
||||
const freshEngine = new WorkflowDiffEngine()
|
||||
const baseline = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1', locked: false }),
|
||||
})
|
||||
|
||||
const proposed = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1', locked: true }),
|
||||
})
|
||||
|
||||
const result = await freshEngine.createDiffFromWorkflowState(
|
||||
proposed,
|
||||
undefined,
|
||||
baseline
|
||||
)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.diff?.diffAnalysis?.edited_blocks ?? []).not.toContain('block-1')
|
||||
expect(
|
||||
result.diff?.diffAnalysis?.field_diffs?.['block-1']?.changed_fields ?? []
|
||||
).not.toContain('locked')
|
||||
}
|
||||
)
|
||||
|
||||
it.concurrent('should not detect change when locked state is the same', async () => {
|
||||
const freshEngine = new WorkflowDiffEngine()
|
||||
const baseline = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1', locked: true }),
|
||||
})
|
||||
|
||||
const proposed = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1', locked: true }),
|
||||
})
|
||||
|
||||
const result = await freshEngine.createDiffFromWorkflowState(proposed, undefined, baseline)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.diff?.diffAnalysis?.edited_blocks ?? []).not.toContain('block-1')
|
||||
})
|
||||
|
||||
it.concurrent(
|
||||
'should NOT detect a diff when locked goes from undefined to true',
|
||||
async () => {
|
||||
const freshEngine = new WorkflowDiffEngine()
|
||||
const baseline = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1' }),
|
||||
})
|
||||
|
||||
const proposed = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1', locked: true }),
|
||||
})
|
||||
|
||||
const result = await freshEngine.createDiffFromWorkflowState(
|
||||
proposed,
|
||||
undefined,
|
||||
baseline
|
||||
)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.diff?.diffAnalysis?.edited_blocks ?? []).not.toContain('block-1')
|
||||
}
|
||||
)
|
||||
|
||||
it.concurrent(
|
||||
'should still detect real edits on a block whose locked state also changed',
|
||||
async () => {
|
||||
const freshEngine = new WorkflowDiffEngine()
|
||||
const baseline = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1', enabled: true, locked: false }),
|
||||
})
|
||||
|
||||
const proposed = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1', enabled: false, locked: true }),
|
||||
})
|
||||
|
||||
const result = await freshEngine.createDiffFromWorkflowState(
|
||||
proposed,
|
||||
undefined,
|
||||
baseline
|
||||
)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.diff?.diffAnalysis?.edited_blocks).toContain('block-1')
|
||||
const changed = result.diff?.diffAnalysis?.field_diffs?.['block-1']?.changed_fields ?? []
|
||||
expect(changed).toContain('enabled')
|
||||
expect(changed).not.toContain('locked')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('parent scope changes', () => {
|
||||
it.concurrent('should detect when a block moves into a subflow', async () => {
|
||||
const freshEngine = new WorkflowDiffEngine()
|
||||
const baseline = createMockWorkflowState({
|
||||
'block-1': createMockBlock({ id: 'block-1' }),
|
||||
})
|
||||
|
||||
const proposed = createMockWorkflowState({
|
||||
'block-1': createMockBlock({
|
||||
id: 'block-1',
|
||||
data: { parentId: 'loop-1', extent: 'parent' },
|
||||
}),
|
||||
})
|
||||
|
||||
const result = await freshEngine.createDiffFromWorkflowState(proposed, undefined, baseline)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.diff?.diffAnalysis?.edited_blocks).toContain('block-1')
|
||||
expect(result.diff?.diffAnalysis?.field_diffs?.['block-1']?.changed_fields).toContain(
|
||||
'parentId'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('diff lifecycle', () => {
|
||||
it.concurrent('should start with no diff', () => {
|
||||
const freshEngine = new WorkflowDiffEngine()
|
||||
expect(freshEngine.hasDiff()).toBe(false)
|
||||
expect(freshEngine.getCurrentDiff()).toBeUndefined()
|
||||
})
|
||||
|
||||
it.concurrent('should clear diff', () => {
|
||||
const freshEngine = new WorkflowDiffEngine()
|
||||
freshEngine.clearDiff()
|
||||
expect(freshEngine.hasDiff()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user