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,351 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*
|
||||
* Integration Tests for Validation Architecture
|
||||
*
|
||||
* These tests verify the complete validation flow:
|
||||
* 1. Early validation (serialization) - user-only required fields
|
||||
* 2. Late validation (tool execution) - user-or-llm required fields
|
||||
*/
|
||||
import { blocksMock } from '@sim/testing/mocks'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Serializer } from '@/serializer/index'
|
||||
|
||||
vi.mock('@/blocks', () => blocksMock)
|
||||
|
||||
/**
|
||||
* Validates required parameters after user and LLM parameter merge.
|
||||
* This checks user-or-llm visibility fields that should have been provided by either source.
|
||||
*/
|
||||
function validateRequiredParametersAfterMerge(
|
||||
toolId: string,
|
||||
tool: any,
|
||||
params: Record<string, any>
|
||||
): void {
|
||||
if (!tool?.params) return
|
||||
|
||||
Object.entries(tool.params).forEach(([paramId, paramConfig]: [string, any]) => {
|
||||
// Only validate user-or-llm visibility fields (user-only are validated earlier)
|
||||
if (paramConfig.required && paramConfig.visibility === 'user-or-llm') {
|
||||
const value = params[paramId]
|
||||
if (value === undefined || value === null || value === '') {
|
||||
// Capitalize first letter of paramId for display
|
||||
const displayName = paramId.charAt(0).toUpperCase() + paramId.slice(1)
|
||||
throw new Error(`${displayName} is required for ${tool.name}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
vi.mock('@/tools/utils', () => ({
|
||||
getTool: (toolId: string) => {
|
||||
const mockTools: Record<string, any> = {
|
||||
jina_read_url: {
|
||||
name: 'Jina Reader',
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
visibility: 'user-or-llm',
|
||||
required: true,
|
||||
description: 'URL to extract content from',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
visibility: 'user-only',
|
||||
required: true,
|
||||
description: 'Your Jina API key',
|
||||
},
|
||||
},
|
||||
},
|
||||
reddit_get_posts: {
|
||||
name: 'Reddit Posts',
|
||||
params: {
|
||||
subreddit: {
|
||||
type: 'string',
|
||||
visibility: 'user-or-llm',
|
||||
required: true,
|
||||
description: 'Subreddit name',
|
||||
},
|
||||
credential: {
|
||||
type: 'string',
|
||||
visibility: 'user-only',
|
||||
required: true,
|
||||
description: 'Reddit credentials',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return mockTools[toolId] || null
|
||||
},
|
||||
validateRequiredParametersAfterMerge,
|
||||
}))
|
||||
|
||||
describe('Validation Integration Tests', () => {
|
||||
it.concurrent('early validation should catch missing user-only fields', () => {
|
||||
const serializer = new Serializer()
|
||||
|
||||
// Block missing user-only field (API key)
|
||||
const blockWithMissingUserOnlyField: any = {
|
||||
id: 'jina-block',
|
||||
type: 'jina',
|
||||
name: 'Jina Content Extractor',
|
||||
position: { x: 0, y: 0 },
|
||||
subBlocks: {
|
||||
url: { value: 'https://example.com' }, // Present
|
||||
apiKey: { value: null }, // Missing user-only field
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
// Should fail at serialization (early validation)
|
||||
expect(() => {
|
||||
serializer.serializeWorkflow(
|
||||
{ 'jina-block': blockWithMissingUserOnlyField },
|
||||
[],
|
||||
{},
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
}).toThrow('Jina Content Extractor is missing required fields: API Key')
|
||||
})
|
||||
|
||||
it.concurrent(
|
||||
'early validation should allow missing user-or-llm fields (LLM can provide later)',
|
||||
() => {
|
||||
const serializer = new Serializer()
|
||||
|
||||
// Block missing user-or-llm field (URL) but has user-only field (API key)
|
||||
const blockWithMissingUserOrLlmField: any = {
|
||||
id: 'jina-block',
|
||||
type: 'jina',
|
||||
name: 'Jina Content Extractor',
|
||||
position: { x: 0, y: 0 },
|
||||
subBlocks: {
|
||||
url: { value: null }, // Missing user-or-llm field (LLM can provide)
|
||||
apiKey: { value: 'test-api-key' }, // Present user-only field
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
// Should pass serialization (early validation doesn't check user-or-llm fields)
|
||||
expect(() => {
|
||||
serializer.serializeWorkflow(
|
||||
{ 'jina-block': blockWithMissingUserOrLlmField },
|
||||
[],
|
||||
{},
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
}).not.toThrow()
|
||||
}
|
||||
)
|
||||
|
||||
it.concurrent(
|
||||
'late validation should catch missing user-or-llm fields after parameter merge',
|
||||
() => {
|
||||
// Simulate parameters after user + LLM merge
|
||||
const mergedParams = {
|
||||
url: null, // Missing user-or-llm field
|
||||
apiKey: 'test-api-key', // Present user-only field
|
||||
}
|
||||
|
||||
// Should fail at tool validation (late validation)
|
||||
expect(() => {
|
||||
validateRequiredParametersAfterMerge(
|
||||
'jina_read_url',
|
||||
{
|
||||
name: 'Jina Reader',
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
visibility: 'user-or-llm',
|
||||
required: true,
|
||||
description: 'URL to extract content from',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
visibility: 'user-only',
|
||||
required: true,
|
||||
description: 'Your Jina API key',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
mergedParams
|
||||
)
|
||||
}).toThrow('Url is required for Jina Reader')
|
||||
}
|
||||
)
|
||||
|
||||
it.concurrent('late validation should NOT validate user-only fields (validated earlier)', () => {
|
||||
// Simulate parameters after user + LLM merge - missing user-only field
|
||||
const mergedParams = {
|
||||
url: 'https://example.com', // Present user-or-llm field
|
||||
apiKey: null, // Missing user-only field (but shouldn't be checked here)
|
||||
}
|
||||
|
||||
// Should pass tool validation (late validation doesn't check user-only fields)
|
||||
expect(() => {
|
||||
validateRequiredParametersAfterMerge(
|
||||
'jina_read_url',
|
||||
{
|
||||
name: 'Jina Reader',
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
visibility: 'user-or-llm',
|
||||
required: true,
|
||||
description: 'URL to extract content from',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
visibility: 'user-only',
|
||||
required: true,
|
||||
description: 'Your Jina API key',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
mergedParams
|
||||
)
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it.concurrent('complete validation flow: both layers working together', () => {
|
||||
const serializer = new Serializer()
|
||||
|
||||
// Scenario 1: Missing user-only field - should fail at serialization
|
||||
const blockMissingUserOnly: any = {
|
||||
id: 'reddit-block',
|
||||
type: 'reddit',
|
||||
name: 'Reddit Posts',
|
||||
position: { x: 0, y: 0 },
|
||||
subBlocks: {
|
||||
operation: { value: 'get_posts' },
|
||||
credential: { value: null }, // Missing user-only
|
||||
subreddit: { value: 'programming' }, // Present user-or-llm
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
serializer.serializeWorkflow(
|
||||
{ 'reddit-block': blockMissingUserOnly },
|
||||
[],
|
||||
{},
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
}).toThrow('Reddit Posts is missing required fields: Reddit Account')
|
||||
|
||||
// Scenario 2: Has user-only fields but missing user-or-llm - should pass serialization
|
||||
const blockMissingUserOrLlm: any = {
|
||||
id: 'reddit-block',
|
||||
type: 'reddit',
|
||||
name: 'Reddit Posts',
|
||||
position: { x: 0, y: 0 },
|
||||
subBlocks: {
|
||||
operation: { value: 'get_posts' },
|
||||
credential: { value: 'reddit-token' }, // Present user-only
|
||||
subreddit: { value: null }, // Missing user-or-llm
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
// Should pass serialization
|
||||
expect(() => {
|
||||
serializer.serializeWorkflow(
|
||||
{ 'reddit-block': blockMissingUserOrLlm },
|
||||
[],
|
||||
{},
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
}).not.toThrow()
|
||||
|
||||
// But should fail at tool validation
|
||||
const mergedParams = {
|
||||
subreddit: null, // Missing user-or-llm field
|
||||
credential: 'reddit-token', // Present user-only field
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
validateRequiredParametersAfterMerge(
|
||||
'reddit_get_posts',
|
||||
{
|
||||
name: 'Reddit Posts',
|
||||
params: {
|
||||
subreddit: {
|
||||
type: 'string',
|
||||
visibility: 'user-or-llm',
|
||||
required: true,
|
||||
description: 'Subreddit name',
|
||||
},
|
||||
credential: {
|
||||
type: 'string',
|
||||
visibility: 'user-only',
|
||||
required: true,
|
||||
description: 'Reddit credentials',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
mergedParams
|
||||
)
|
||||
}).toThrow('Subreddit is required for Reddit Posts')
|
||||
})
|
||||
|
||||
it.concurrent('complete success: all required fields provided correctly', () => {
|
||||
const serializer = new Serializer()
|
||||
|
||||
// Block with all required fields present
|
||||
const completeBlock: any = {
|
||||
id: 'jina-block',
|
||||
type: 'jina',
|
||||
name: 'Jina Content Extractor',
|
||||
position: { x: 0, y: 0 },
|
||||
subBlocks: {
|
||||
url: { value: 'https://example.com' }, // Present user-or-llm
|
||||
apiKey: { value: 'test-api-key' }, // Present user-only
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
// Should pass serialization (early validation)
|
||||
expect(() => {
|
||||
serializer.serializeWorkflow({ 'jina-block': completeBlock }, [], {}, undefined, true)
|
||||
}).not.toThrow()
|
||||
|
||||
// Should pass tool validation (late validation)
|
||||
const completeParams = {
|
||||
url: 'https://example.com',
|
||||
apiKey: 'test-api-key',
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
validateRequiredParametersAfterMerge(
|
||||
'jina_read_url',
|
||||
{
|
||||
name: 'Jina Reader',
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
visibility: 'user-or-llm',
|
||||
required: true,
|
||||
description: 'URL to extract content from',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
visibility: 'user-only',
|
||||
required: true,
|
||||
description: 'Your Jina API key',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
completeParams
|
||||
)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user