Files
simstudioai--sim/apps/sim/ee/workspace-forking/lib/remap/block-identity.test.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

89 lines
3.5 KiB
TypeScript

/**
* @vitest-environment node
*/
import { isValidUuid } from '@sim/utils/id'
import { describe, expect, it } from 'vitest'
import {
buildForkBlockIdResolver,
deriveForkBlockId,
EMPTY_FORK_BLOCK_MAP,
type ForkBlockMap,
} from '@/ee/workspace-forking/lib/remap/block-identity'
describe('deriveForkBlockId', () => {
const targetA = 'wf-target-a'
const targetB = 'wf-target-b'
const block1 = 'block-1'
const block2 = 'block-2'
it('is deterministic for the same (targetWorkflowId, sourceBlockId)', () => {
expect(deriveForkBlockId(targetA, block1)).toBe(deriveForkBlockId(targetA, block1))
})
it('yields different ids for the same source block in different target workflows', () => {
expect(deriveForkBlockId(targetA, block1)).not.toBe(deriveForkBlockId(targetB, block1))
})
it('yields different ids for different source blocks in the same target workflow', () => {
expect(deriveForkBlockId(targetA, block1)).not.toBe(deriveForkBlockId(targetA, block2))
})
it('produces a valid UUID string (v5)', () => {
const id = deriveForkBlockId(targetA, block1)
expect(isValidUuid(id)).toBe(true)
expect(id[14]).toBe('5')
})
it('does not collide the colon separator (a:bc vs ab:c)', () => {
expect(deriveForkBlockId('a', 'bc')).not.toBe(deriveForkBlockId('ab', 'c'))
})
})
describe('buildForkBlockIdResolver', () => {
const parentWf = 'wf-parent'
const childWf = 'wf-child'
const parentBlock = 'block-parent'
// The pair the fork created: child block derived from the parent block.
const childBlock = deriveForkBlockId(childWf, parentBlock)
const seededMap: ForkBlockMap = {
parentToChild: new Map([
[parentBlock, { targetBlockId: childBlock, targetWorkflowId: childWf }],
]),
childToParent: new Map([
[childBlock, { targetBlockId: parentBlock, targetWorkflowId: parentWf }],
]),
}
it('push maps a child block back to the parent ORIGINAL id (keeps the webhook URL stable)', () => {
const pushResolve = buildForkBlockIdResolver(false, seededMap)
expect(pushResolve(parentWf, childBlock)).toBe(parentBlock)
// The bug this fixes: without the map, push would re-derive and re-key the parent block.
expect(pushResolve(parentWf, childBlock)).not.toBe(deriveForkBlockId(parentWf, childBlock))
})
it('pull maps a parent block to its existing child id', () => {
const pullResolve = buildForkBlockIdResolver(true, seededMap)
expect(pullResolve(childWf, parentBlock)).toBe(childBlock)
})
it('derives (does NOT reuse) when the target workflow was re-created (different id)', () => {
// Parent workflow archived + re-created as wf-parent-2: the pair points at the old
// workflow, so reusing parentBlock there would collide on the global block PK. Derive.
const pushResolve = buildForkBlockIdResolver(false, seededMap)
expect(pushResolve('wf-parent-2', childBlock)).toBe(
deriveForkBlockId('wf-parent-2', childBlock)
)
expect(pushResolve('wf-parent-2', childBlock)).not.toBe(parentBlock)
})
it('derives a fresh id for a source block with no recorded pair (added since last sync)', () => {
const pushResolve = buildForkBlockIdResolver(false, seededMap)
expect(pushResolve(parentWf, 'block-new')).toBe(deriveForkBlockId(parentWf, 'block-new'))
})
it('derives everything when the map is empty (fork creation)', () => {
const resolve = buildForkBlockIdResolver(true, EMPTY_FORK_BLOCK_MAP)
expect(resolve(childWf, parentBlock)).toBe(deriveForkBlockId(childWf, parentBlock))
})
})