|
|
@@ -0,0 +1,473 @@
|
|
|
+/**
|
|
|
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
|
+ * SPDX-License-Identifier: MIT
|
|
|
+ */
|
|
|
+
|
|
|
+import { describe, expect, it } from 'vitest';
|
|
|
+import { IContainer, IEngine, WorkflowStatus } from '@flowgram.ai/runtime-interface';
|
|
|
+
|
|
|
+import { WorkflowRuntimeContainer } from '../../container';
|
|
|
+import { ValidateInputsSchemaInputs } from './validate-inputs';
|
|
|
+import { TestSchemas } from '.';
|
|
|
+
|
|
|
+const container: IContainer = WorkflowRuntimeContainer.instance;
|
|
|
+
|
|
|
+describe('WorkflowRuntime validate inputs success', () => {
|
|
|
+ it('basic inputs', async () => {
|
|
|
+ const inputs: ValidateInputsSchemaInputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: {
|
|
|
+ CEA: 'nested string',
|
|
|
+ },
|
|
|
+ CF: ['item1', 'item2', 'item3'],
|
|
|
+ },
|
|
|
+ DD: [
|
|
|
+ {
|
|
|
+ DA: 'optional string',
|
|
|
+ DB: {
|
|
|
+ DBA: 'deep nested',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ EE: {
|
|
|
+ EA: {
|
|
|
+ EAA: 'required nested',
|
|
|
+ },
|
|
|
+ EB: 'optional string',
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context, processing } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
|
|
|
+ const result = await processing;
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
|
|
|
+ expect(result).toStrictEqual(inputs);
|
|
|
+ });
|
|
|
+ it('complex inputs', async () => {
|
|
|
+ const inputs: ValidateInputsSchemaInputs = {
|
|
|
+ AA: 'complex example',
|
|
|
+ BB: -999,
|
|
|
+ CC: {
|
|
|
+ CA: 'test',
|
|
|
+ CB: 0,
|
|
|
+ CC: 999999,
|
|
|
+ CD: false,
|
|
|
+ CE: {
|
|
|
+ CEA: 'another nested value',
|
|
|
+ },
|
|
|
+ CF: ['a', 'b', 'c', 'd', 'e'],
|
|
|
+ },
|
|
|
+ DD: [
|
|
|
+ {
|
|
|
+ DA: 'first item',
|
|
|
+ DB: {
|
|
|
+ DBA: 'first nested',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ DA: 'second item',
|
|
|
+ // DB is optional, omitted here
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // DA is optional, omitted here
|
|
|
+ DB: {
|
|
|
+ DBA: 'third nested',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ EE: {
|
|
|
+ EA: {
|
|
|
+ EAA: 'required value',
|
|
|
+ },
|
|
|
+ // EB is optional, omitted here
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context, processing } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
|
|
|
+ const result = await processing;
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
|
|
|
+ expect(result).toStrictEqual(inputs);
|
|
|
+ });
|
|
|
+ it('min inputs', async () => {
|
|
|
+ const inputs: ValidateInputsSchemaInputs = {
|
|
|
+ AA: '',
|
|
|
+ BB: 0,
|
|
|
+ CC: {
|
|
|
+ CA: '',
|
|
|
+ CB: 0,
|
|
|
+ CC: 0,
|
|
|
+ CD: false,
|
|
|
+ CE: {
|
|
|
+ CEA: '',
|
|
|
+ },
|
|
|
+ CF: [],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: {
|
|
|
+ EAA: '',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context, processing } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
|
|
|
+ const result = await processing;
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
|
|
|
+ expect(result).toStrictEqual(inputs);
|
|
|
+ });
|
|
|
+ it('full inputs', async () => {
|
|
|
+ const inputs: ValidateInputsSchemaInputs = {
|
|
|
+ AA: 'full example',
|
|
|
+ BB: 12345,
|
|
|
+ CC: {
|
|
|
+ CA: 'complete',
|
|
|
+ CB: 500,
|
|
|
+ CC: 600,
|
|
|
+ CD: true,
|
|
|
+ CE: {
|
|
|
+ CEA: 'full nested',
|
|
|
+ },
|
|
|
+ CF: ['full', 'array', 'example'],
|
|
|
+ },
|
|
|
+ DD: [
|
|
|
+ {
|
|
|
+ DA: 'with all fields',
|
|
|
+ DB: {
|
|
|
+ DBA: 'complete nested',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ EE: {
|
|
|
+ EA: {
|
|
|
+ EAA: 'all required',
|
|
|
+ },
|
|
|
+ EB: 'with optional field',
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context, processing } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
|
|
|
+ const result = await processing;
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
|
|
|
+ expect(result).toStrictEqual(inputs);
|
|
|
+ });
|
|
|
+ it('edge inputs', async () => {
|
|
|
+ const inputs: ValidateInputsSchemaInputs = {
|
|
|
+ AA: 'a', // single character
|
|
|
+ BB: Number.MAX_SAFE_INTEGER,
|
|
|
+ CC: {
|
|
|
+ CA: 'very long string that tests the boundaries of what might be acceptable in real world scenarios',
|
|
|
+ CB: Number.MIN_SAFE_INTEGER,
|
|
|
+ CC: 1,
|
|
|
+ CD: true,
|
|
|
+ CE: {
|
|
|
+ CEA: 'boundary test',
|
|
|
+ },
|
|
|
+ CF: ['single'],
|
|
|
+ },
|
|
|
+ DD: Array(100).fill({
|
|
|
+ DA: 'repeated',
|
|
|
+ DB: {
|
|
|
+ DBA: 'many items',
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ EE: {
|
|
|
+ EA: {
|
|
|
+ EAA: 'boundary',
|
|
|
+ },
|
|
|
+ EB: '',
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context, processing } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
|
|
|
+ const result = await processing;
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
|
|
|
+ expect(result).toStrictEqual(inputs);
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+describe('WorkflowRuntime validate inputs failed', () => {
|
|
|
+ it('missing required property "AA"', async () => {
|
|
|
+ const inputs = {
|
|
|
+ // AA: "missing", // ❌ missing required property AA
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: { EAA: 'required' },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Missing required property "AA" at root'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('property "AA" expected to be string, not number', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 123, // ❌ AA should be string, not number
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: { EAA: 'required' },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Expected string at AA, but got: number'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('property "BB" expected to be number, not string', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 'test-string', // ❌ BB should be number, not string
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: { EAA: 'required' },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Expected integer at BB, but got: "test-string"'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('property "CC.CA" expected to be string, not number', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ // CA: 123, // ❌ CA should be string, not number
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: { EAA: 'required' },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Missing required property "CA" at CC'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('missing required property "CC.CEA"', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: {
|
|
|
+ // CEA: "missing" // ❌ missing required property CEA
|
|
|
+ },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: { EAA: 'required' },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Missing required property "CEA" at CC.CE'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('xxxxxxxxxxxxxxxxx', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: [1, 2, 3], // ❌ CF should be string[], not number[]
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: { EAA: 'required' },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Expected string at CC.CF[0], but got: number'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('property "DD" expected to be array, not string', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: 'not an array', // ❌ DD should be array, not string
|
|
|
+ EE: {
|
|
|
+ EA: { EAA: 'required' },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Expected array at DD, but got: string'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('missing required property "EE"', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ // EE: { ... } // ❌ missing required property EE
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Missing required property "EE" at root'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ it('property "EE.EA.EAA" expected to be string, not boolean', async () => {
|
|
|
+ const inputs = {
|
|
|
+ AA: 'hello',
|
|
|
+ BB: 42,
|
|
|
+ CC: {
|
|
|
+ CA: 'world',
|
|
|
+ CB: 100,
|
|
|
+ CC: 200,
|
|
|
+ CD: true,
|
|
|
+ CE: { CEA: 'nested' },
|
|
|
+ CF: ['item1'],
|
|
|
+ },
|
|
|
+ DD: [],
|
|
|
+ EE: {
|
|
|
+ EA: {
|
|
|
+ EAA: true, // ❌ EAA should be string, not boolean
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const engine = container.get<IEngine>(IEngine);
|
|
|
+ const { context } = engine.invoke({
|
|
|
+ schema: TestSchemas.validateInputsSchema,
|
|
|
+ inputs,
|
|
|
+ });
|
|
|
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Failed);
|
|
|
+ const report = context.reporter.export();
|
|
|
+ expect(report.messages.error[0].message).toBe(
|
|
|
+ 'JSON Schema validation failed: Expected string at EE.EA.EAA, but got: boolean'
|
|
|
+ );
|
|
|
+ });
|
|
|
+});
|