d25d482dc2
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
89 lines
3.5 KiB
TypeScript
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))
|
|
})
|
|
})
|