chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { cacheLargeValue, clearLargeValueCacheForTests } from '@/lib/execution/payloads/cache'
|
||||
import {
|
||||
LARGE_ARRAY_MANIFEST_MARKER,
|
||||
LARGE_ARRAY_MANIFEST_VERSION,
|
||||
} from '@/lib/execution/payloads/large-array-manifest-metadata'
|
||||
import { LARGE_VALUE_REF_MARKER } from '@/lib/execution/payloads/large-value-ref'
|
||||
import type { ExecutionContext } from '@/executor/types'
|
||||
import { findEffectiveContainerId } from '@/executor/utils/subflow-utils'
|
||||
import { resolveArrayInputAsync } from '@/executor/utils/subflow-utils.server'
|
||||
import type { VariableResolver } from '@/executor/variables/resolver'
|
||||
|
||||
describe('resolveArrayInputAsync', () => {
|
||||
const fakeCtx = {
|
||||
workspaceId: 'workspace-1',
|
||||
workflowId: 'workflow-1',
|
||||
executionId: 'execution-1',
|
||||
userId: 'user-1',
|
||||
} as unknown as ExecutionContext
|
||||
|
||||
beforeEach(() => {
|
||||
clearLargeValueCacheForTests()
|
||||
})
|
||||
|
||||
function createManifest(items: unknown[]) {
|
||||
const json = JSON.stringify(items)
|
||||
const size = Buffer.byteLength(json, 'utf8')
|
||||
const id = 'lv_ABCDEFGHIJKL'
|
||||
cacheLargeValue(id, items, size, fakeCtx)
|
||||
|
||||
return {
|
||||
__simLargeArrayManifest: true,
|
||||
version: LARGE_ARRAY_MANIFEST_VERSION,
|
||||
kind: 'array',
|
||||
totalCount: items.length,
|
||||
chunkCount: 1,
|
||||
byteSize: size,
|
||||
chunks: [
|
||||
{
|
||||
ref: {
|
||||
__simLargeValueRef: true,
|
||||
version: 1,
|
||||
id,
|
||||
kind: 'array',
|
||||
size,
|
||||
executionId: fakeCtx.executionId,
|
||||
},
|
||||
count: items.length,
|
||||
byteSize: size,
|
||||
},
|
||||
],
|
||||
preview: items.slice(0, 3),
|
||||
}
|
||||
}
|
||||
|
||||
it('returns arrays as-is', async () => {
|
||||
await expect(resolveArrayInputAsync(fakeCtx, [1, 2, 3], null)).resolves.toEqual([1, 2, 3])
|
||||
})
|
||||
|
||||
it('converts plain objects to entries', async () => {
|
||||
await expect(resolveArrayInputAsync(fakeCtx, { a: 1, b: 2 }, null)).resolves.toEqual([
|
||||
['a', 1],
|
||||
['b', 2],
|
||||
])
|
||||
})
|
||||
|
||||
it('materializes large array manifests instead of iterating metadata entries', async () => {
|
||||
const items = [{ id: 1 }, { id: 2 }]
|
||||
const manifest = createManifest(items)
|
||||
|
||||
await expect(resolveArrayInputAsync(fakeCtx, manifest, null)).resolves.toEqual(items)
|
||||
})
|
||||
|
||||
it('records exact nested keys discovered while materializing collection values', async () => {
|
||||
const ctx = {
|
||||
...fakeCtx,
|
||||
largeValueKeys: [] as string[],
|
||||
fileKeys: [] as string[],
|
||||
} as ExecutionContext
|
||||
const nestedRef = {
|
||||
__simLargeValueRef: true,
|
||||
version: 1,
|
||||
id: 'lv_MNOPQRSTUVWX',
|
||||
kind: 'object',
|
||||
size: 12,
|
||||
key: 'execution/workspace-1/workflow-1/source-execution/large-value-lv_MNOPQRSTUVWX.json',
|
||||
executionId: 'source-execution',
|
||||
}
|
||||
const file = {
|
||||
id: 'file-1',
|
||||
name: 'nested.txt',
|
||||
key: 'execution/workspace-1/workflow-1/source-execution/nested.txt',
|
||||
url: '/api/files/serve/execution/workspace-1/workflow-1/source-execution/nested.txt?context=execution',
|
||||
size: 5,
|
||||
type: 'text/plain',
|
||||
context: 'execution',
|
||||
}
|
||||
const items = [{ nestedRef, file }]
|
||||
const manifest = createManifest(items)
|
||||
|
||||
await expect(resolveArrayInputAsync(ctx, manifest, null)).resolves.toEqual(items)
|
||||
|
||||
expect(ctx.largeValueKeys).toEqual([nestedRef.key])
|
||||
expect(ctx.fileKeys).toEqual([file.key])
|
||||
})
|
||||
|
||||
it('rejects invalid manifest-shaped collection inputs instead of iterating metadata', async () => {
|
||||
await expect(
|
||||
resolveArrayInputAsync(
|
||||
fakeCtx,
|
||||
{
|
||||
[LARGE_ARRAY_MANIFEST_MARKER]: true,
|
||||
version: LARGE_ARRAY_MANIFEST_VERSION,
|
||||
kind: 'array',
|
||||
totalCount: 1,
|
||||
chunkCount: 0,
|
||||
byteSize: 0,
|
||||
chunks: [],
|
||||
preview: [],
|
||||
},
|
||||
null
|
||||
)
|
||||
).rejects.toThrow('Invalid large array manifest')
|
||||
})
|
||||
|
||||
it('rejects invalid large-ref-shaped collection inputs instead of iterating metadata', async () => {
|
||||
await expect(
|
||||
resolveArrayInputAsync(
|
||||
fakeCtx,
|
||||
{
|
||||
[LARGE_VALUE_REF_MARKER]: true,
|
||||
version: 1,
|
||||
id: 'not-a-valid-large-value-id',
|
||||
kind: 'array',
|
||||
size: 1,
|
||||
},
|
||||
null
|
||||
)
|
||||
).rejects.toThrow('Invalid large value ref')
|
||||
})
|
||||
|
||||
it('returns empty array when a pure reference resolves to null (skipped block)', async () => {
|
||||
// `resolveSingleReference` returns `null` for a reference that points at a
|
||||
// block that exists in the workflow but did not execute on this path.
|
||||
// A loop/parallel over such a reference should run zero iterations rather
|
||||
// than fail the workflow.
|
||||
const resolver = {
|
||||
resolveSingleReference: vi.fn().mockResolvedValue(null),
|
||||
} as unknown as VariableResolver
|
||||
|
||||
const result = await resolveArrayInputAsync(fakeCtx, '<SkippedBlock.result.items>', resolver)
|
||||
|
||||
expect(result).toEqual([])
|
||||
expect(resolver.resolveSingleReference).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns the array from a pure reference that resolved to an array', async () => {
|
||||
const resolver = {
|
||||
resolveSingleReference: vi.fn().mockResolvedValue([1, 2, 3]),
|
||||
} as unknown as VariableResolver
|
||||
|
||||
await expect(resolveArrayInputAsync(fakeCtx, '<Block.items>', resolver)).resolves.toEqual([
|
||||
1, 2, 3,
|
||||
])
|
||||
})
|
||||
|
||||
it('materializes a manifest returned by a pure reference', async () => {
|
||||
const items = [{ id: 1 }, { id: 2 }]
|
||||
const manifest = createManifest(items)
|
||||
const resolver = {
|
||||
resolveSingleReference: vi.fn().mockResolvedValue(manifest),
|
||||
} as unknown as VariableResolver
|
||||
|
||||
await expect(resolveArrayInputAsync(fakeCtx, '<variable.issues>', resolver)).resolves.toEqual(
|
||||
items
|
||||
)
|
||||
expect(resolver.resolveSingleReference).toHaveBeenCalledWith(
|
||||
fakeCtx,
|
||||
'',
|
||||
'<variable.issues>',
|
||||
undefined,
|
||||
{ allowLargeValueRefs: true }
|
||||
)
|
||||
})
|
||||
|
||||
it('converts resolved objects to entries', async () => {
|
||||
const resolver = {
|
||||
resolveSingleReference: vi.fn().mockResolvedValue({ x: 1, y: 2 }),
|
||||
} as unknown as VariableResolver
|
||||
|
||||
await expect(resolveArrayInputAsync(fakeCtx, '<Block.obj>', resolver)).resolves.toEqual([
|
||||
['x', 1],
|
||||
['y', 2],
|
||||
])
|
||||
})
|
||||
|
||||
it('throws when a pure reference resolves to a non-array, non-object, non-null value', async () => {
|
||||
const resolver = {
|
||||
resolveSingleReference: vi.fn().mockResolvedValue(42),
|
||||
} as unknown as VariableResolver
|
||||
|
||||
await expect(resolveArrayInputAsync(fakeCtx, '<Block.count>', resolver)).rejects.toThrow(
|
||||
/did not resolve to an array or object/
|
||||
)
|
||||
})
|
||||
|
||||
it('throws when a pure reference resolves to undefined (unknown block)', async () => {
|
||||
// `undefined` means the reference could not be matched to any block at
|
||||
// all (typo / deleted block). This must still fail loudly.
|
||||
const resolver = {
|
||||
resolveSingleReference: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as VariableResolver
|
||||
|
||||
await expect(resolveArrayInputAsync(fakeCtx, '<Missing.items>', resolver)).rejects.toThrow(
|
||||
/did not resolve to an array or object/
|
||||
)
|
||||
})
|
||||
|
||||
it('parses a JSON array string', async () => {
|
||||
await expect(resolveArrayInputAsync(fakeCtx, '[1, 2, 3]', null)).resolves.toEqual([1, 2, 3])
|
||||
})
|
||||
|
||||
it('throws on a string that is neither a reference nor valid JSON array/object', async () => {
|
||||
await expect(resolveArrayInputAsync(fakeCtx, 'not json', null)).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('findEffectiveContainerId', () => {
|
||||
it('finds pre-cloned nested subflow IDs with clone sequence suffixes', () => {
|
||||
const executionMap = new Map<string, unknown>([
|
||||
['inner-parallel', {}],
|
||||
['inner-parallel__obranch-2', {}],
|
||||
['inner-parallel__clone3__obranch-2', {}],
|
||||
])
|
||||
|
||||
expect(
|
||||
findEffectiveContainerId('inner-parallel', 'leaf__clone7__obranch-2₍0₎', executionMap)
|
||||
).toBe('inner-parallel__clone3__obranch-2')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user