| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965 |
- package workflow
- import (
- "context"
- "encoding/json"
- "fmt"
- "testing"
- )
- // TestIsStructuredOutput tests the isStructuredOutput helper function
- func TestIsStructuredOutput(t *testing.T) {
- tests := []struct {
- name string
- params map[string]interface{}
- expected bool
- }{
- {
- name: "JSONSchemaFormat",
- params: map[string]interface{}{
- "response_format": map[string]interface{}{
- "type": "json_schema",
- },
- },
- expected: true,
- },
- {
- name: "TextFormat",
- params: map[string]interface{}{
- "response_format": map[string]interface{}{
- "type": "text",
- },
- },
- expected: false,
- },
- {
- name: "NoResponseFormat",
- params: map[string]interface{}{},
- expected: false,
- },
- {
- name: "OutputConfigJSONSchema",
- params: map[string]interface{}{
- "output_config": map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- },
- },
- },
- expected: true,
- },
- {
- name: "OutputConfigText",
- params: map[string]interface{}{
- "output_config": map[string]interface{}{
- "format": map[string]interface{}{
- "type": "text",
- },
- },
- },
- expected: false,
- },
- {
- name: "BothFormats",
- params: map[string]interface{}{
- "response_format": map[string]interface{}{
- "type": "json_schema",
- },
- "output_config": map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- },
- },
- },
- expected: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := isStructuredOutput(tt.params); got != tt.expected {
- t.Errorf("isStructuredOutput() = %v, want %v", got, tt.expected)
- }
- })
- }
- }
- // TestStructuredOutputIntegration tests the v3.7 structured output feature integration
- func TestStructuredOutputIntegration(t *testing.T) {
- // Create mock LLM adapter that simulates structured output
- mockAdapter := NewDefaultLLMAdapter()
- mockJSON := `{"score": 85, "summary": "Good code quality"}`
- mockAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- result := map[string]interface{}{
- "content": mockJSON,
- "model": "gpt-4",
- "finish_reason": "stop",
- }
- // Simulate structured output parsing (like OpenAIAdapter does)
- if isStructuredOutput(params) {
- var parsed interface{}
- if err := json.Unmarshal([]byte(mockJSON), &parsed); err != nil {
- return nil, err
- }
- result["content"] = parsed
- }
- return result, nil
- })
- // Create workflow with LLM step using structured output
- wf := &Workflow{
- Version: "3.7",
- Name: "Structured Output Test",
- Registry: Registry{
- Vars: []string{
- "$result(OBJECT)",
- },
- },
- Steps: []Step{
- {
- ID: "LLM_Test",
- In: StepInput{
- "model": "gpt-4",
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Generate a code review",
- },
- },
- "response_format": map[string]interface{}{
- "type": "json_schema",
- "json_schema": map[string]interface{}{
- "name": "code_review",
- "description": "A code review response",
- "schema": map[string]interface{}{
- "type": "object",
- "properties": map[string]interface{}{
- "score": map[string]interface{}{
- "type": "integer",
- },
- "summary": map[string]interface{}{
- "type": "string",
- },
- },
- "required": []interface{}{"score", "summary"},
- },
- },
- },
- },
- Out: StepOutput{
- "$result": "=_result",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- engine, err := NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- adapters := &Adapters{
- LLM: mockAdapter,
- Service: NewDefaultServiceAdapter(),
- }
- // Execute workflow
- result, err := engine.Execute(context.Background(), nil, adapters)
- if err != nil {
- t.Fatalf("Workflow execution failed: %v", err)
- }
- // Consume events to wait for workflow completion
- for range result.RunEventStream {
- }
- // Check that $result contains the parsed JSON object
- resultVar, ok := result.Context.Variables["$result"]
- if !ok {
- t.Fatal("$result not found in variables")
- }
- // Verify it's a parsed object (map), not a string
- resultMap, ok := resultVar.(map[string]interface{})
- if !ok {
- t.Fatalf("Expected $result to be a map[string]interface{}, got %T", resultVar)
- }
- // Check the parsed fields
- if score, ok := resultMap["score"].(float64); !ok || score != 85 {
- t.Errorf("Expected score to be 85, got %v", resultMap["score"])
- }
- if summary, ok := resultMap["summary"].(string); !ok || summary != "Good code quality" {
- t.Errorf("Expected summary to be 'Good code quality', got %v", resultMap["summary"])
- }
- }
- // TestOutputConfigIntegration tests the v3.8 output_config direct passthrough feature
- func TestOutputConfigIntegration(t *testing.T) {
- // Create mock LLM adapter that simulates structured output
- mockAdapter := NewDefaultLLMAdapter()
- mockJSON := `{"score": 92, "feedback": "Excellent implementation"}`
- mockAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- result := map[string]interface{}{
- "content": mockJSON,
- "model": "claude-sonnet-4-5",
- "finish_reason": "stop",
- }
- // Simulate structured output parsing (like OpenAIAdapter does)
- if isStructuredOutput(params) {
- var parsed interface{}
- if err := json.Unmarshal([]byte(mockJSON), &parsed); err != nil {
- return nil, err
- }
- result["content"] = parsed
- }
- return result, nil
- })
- // Create workflow with LLM step using output_config (Anthropic style)
- wf := &Workflow{
- Version: "3.8",
- Name: "Output Config Test",
- Registry: Registry{
- Vars: []string{
- "$result(OBJECT)",
- },
- },
- Steps: []Step{
- {
- ID: "LLM_Test",
- In: StepInput{
- "model": "claude-sonnet-4-5",
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Generate a performance review",
- },
- },
- "output_config": map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- "schema": map[string]interface{}{
- "type": "object",
- "properties": map[string]interface{}{
- "score": map[string]interface{}{
- "type": "integer",
- },
- "feedback": map[string]interface{}{
- "type": "string",
- },
- },
- "required": []interface{}{"score", "feedback"},
- "additionalProperties": false,
- },
- },
- },
- },
- Out: StepOutput{
- "$result": "=_result",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- engine, err := NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- adapters := &Adapters{
- LLM: mockAdapter,
- Service: NewDefaultServiceAdapter(),
- }
- // Execute workflow
- result, err := engine.Execute(context.Background(), nil, adapters)
- if err != nil {
- t.Fatalf("Workflow execution failed: %v", err)
- }
- // Consume events to wait for workflow completion
- for range result.RunEventStream {
- }
- // Check that $result contains the parsed JSON object
- resultVar, ok := result.Context.Variables["$result"]
- if !ok {
- t.Fatal("$result not found in variables")
- }
- // Verify it's a parsed object (map), not a string
- resultMap, ok := resultVar.(map[string]interface{})
- if !ok {
- t.Fatalf("Expected $result to be a map[string]interface{}, got %T", resultVar)
- }
- // Check the parsed fields
- if score, ok := resultMap["score"].(float64); !ok || score != 92 {
- t.Errorf("Expected score to be 92, got %v", resultMap["score"])
- }
- if feedback, ok := resultMap["feedback"].(string); !ok || feedback != "Excellent implementation" {
- t.Errorf("Expected feedback to be 'Excellent implementation', got %v", resultMap["feedback"])
- }
- }
- // TestApplyOutputConfig tests the applyOutputConfig method
- func TestApplyOutputConfig(t *testing.T) {
- adapter := NewOpenAIAdapter(OpenAIConfig{
- BaseURL: "http://localhost:4000",
- })
- tests := []struct {
- name string
- outputConfig map[string]interface{}
- wantError bool
- validate func(t *testing.T, req *ChatCompletionRequest)
- }{
- {
- name: "ValidJSONSchema",
- outputConfig: map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- "schema": map[string]interface{}{
- "type": "object",
- "properties": map[string]interface{}{
- "result": map[string]interface{}{"type": "string"},
- },
- "additionalProperties": false,
- },
- },
- },
- wantError: false,
- validate: func(t *testing.T, req *ChatCompletionRequest) {
- if req.OutputConfig == nil {
- t.Error("Expected OutputConfig to be set")
- return
- }
- if req.OutputConfig.Format == nil {
- t.Error("Expected OutputConfig.Format to be set")
- return
- }
- if req.OutputConfig.Format.Type != "json_schema" {
- t.Errorf("Expected Format.Type = json_schema, got %s", req.OutputConfig.Format.Type)
- }
- if req.OutputConfig.Format.Schema == nil {
- t.Error("Expected Schema to be set")
- return
- }
- },
- },
- {
- name: "TextFormat",
- outputConfig: map[string]interface{}{
- "format": map[string]interface{}{
- "type": "text",
- },
- },
- wantError: false,
- validate: func(t *testing.T, req *ChatCompletionRequest) {
- // Text format should not set OutputConfig
- if req.OutputConfig != nil {
- t.Error("Expected OutputConfig to be nil for text format")
- }
- },
- },
- {
- name: "MissingFormat",
- outputConfig: map[string]interface{}{
- "type": "json_schema",
- },
- wantError: true,
- },
- {
- name: "MissingSchema",
- outputConfig: map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- },
- },
- wantError: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- req := &ChatCompletionRequest{
- Model: "claude-sonnet-4-5",
- }
- err := adapter.applyOutputConfig(req, tt.outputConfig)
- if (err != nil) != tt.wantError {
- t.Errorf("applyOutputConfig() error = %v, wantError %v", err, tt.wantError)
- return
- }
- if !tt.wantError && tt.validate != nil {
- tt.validate(t, req)
- }
- })
- }
- }
- // TestStructuredOutputToStringVariable tests that structured output (JSON object) is correctly
- // marshaled to JSON string when assigned to a STRING variable
- func TestStructuredOutputToStringVariable(t *testing.T) {
- // Create mock LLM adapter that simulates structured output
- mockAdapter := NewDefaultLLMAdapter()
- mockJSON := `{"name": "John", "age": 30, "active": true}`
- mockAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- result := map[string]interface{}{
- "content": mockJSON,
- "model": "claude-sonnet-4-5",
- "finish_reason": "stop",
- }
- // Simulate structured output parsing
- if isStructuredOutput(params) {
- var parsed interface{}
- if err := json.Unmarshal([]byte(mockJSON), &parsed); err != nil {
- return nil, err
- }
- result["content"] = parsed
- }
- return result, nil
- })
- // Create workflow with LLM step that outputs to a STRING variable
- wf := &Workflow{
- Version: "3.8",
- Name: "Structured Output to String Test",
- Registry: Registry{
- Vars: []string{
- "$userInfo(STRING)", // Declaring as STRING type
- },
- },
- Steps: []Step{
- {
- ID: "LLM_GetUser",
- In: StepInput{
- "model": "claude-sonnet-4-5",
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Get user info",
- },
- },
- "output_config": map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- "schema": map[string]interface{}{
- "type": "object",
- "properties": map[string]interface{}{
- "name": map[string]interface{}{
- "type": "string",
- },
- "age": map[string]interface{}{
- "type": "integer",
- },
- "active": map[string]interface{}{
- "type": "boolean",
- },
- },
- "required": []interface{}{"name", "age", "active"},
- "additionalProperties": false,
- },
- },
- },
- },
- Out: StepOutput{
- "$userInfo": "=_result", // Assigning JSON object to STRING variable
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- engine, err := NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- adapters := &Adapters{
- LLM: mockAdapter,
- Service: NewDefaultServiceAdapter(),
- }
- // Execute workflow
- result, err := engine.Execute(context.Background(), nil, adapters)
- if err != nil {
- t.Fatalf("Workflow execution failed: %v", err)
- }
- // Consume events to wait for workflow completion
- for range result.RunEventStream {
- }
- // Check that $userInfo is a STRING (JSON marshaled)
- userInfo, ok := result.Context.Variables["$userInfo"]
- if !ok {
- t.Fatal("$userInfo not found in variables")
- }
- // Verify it's a string (JSON marshaled from object)
- userInfoStr, ok := userInfo.(string)
- if !ok {
- t.Fatalf("Expected $userInfo to be a string, got %T", userInfo)
- }
- // Parse the JSON string to verify it's valid JSON
- var parsed map[string]interface{}
- if err := json.Unmarshal([]byte(userInfoStr), &parsed); err != nil {
- t.Fatalf("Expected $userInfo to be valid JSON string, got parse error: %v (value: %q)", err, userInfoStr)
- }
- // Verify the content is correct
- if name, ok := parsed["name"].(string); !ok || name != "John" {
- t.Errorf("Expected name to be 'John', got %v", parsed["name"])
- }
- if age, ok := parsed["age"].(float64); !ok || age != 30 {
- t.Errorf("Expected age to be 30, got %v", parsed["age"])
- }
- if active, ok := parsed["active"].(bool); !ok || active != true {
- t.Errorf("Expected active to be true, got %v", parsed["active"])
- }
- }
- // TestVendorParameterMapping tests that response_format is correctly mapped to vendor-specific formats
- func TestVendorParameterMapping(t *testing.T) {
- tests := []struct {
- name string
- model string
- responseFormat map[string]interface{}
- expectOutputConfig bool
- expectResponseFormat bool
- }{
- {
- name: "AnthropicModel",
- model: "claude-3-5-sonnet-20241022",
- responseFormat: map[string]interface{}{
- "type": "json_schema",
- "json_schema": map[string]interface{}{
- "name": "test_schema",
- "schema": map[string]interface{}{
- "type": "object",
- "properties": map[string]interface{}{
- "result": map[string]interface{}{"type": "string"},
- },
- },
- },
- },
- expectOutputConfig: true,
- expectResponseFormat: false,
- },
- {
- name: "OpenAIModel",
- model: "gpt-4o",
- responseFormat: map[string]interface{}{
- "type": "json_schema",
- "json_schema": map[string]interface{}{
- "name": "test_schema",
- "schema": map[string]interface{}{
- "type": "object",
- "properties": map[string]interface{}{
- "result": map[string]interface{}{"type": "string"},
- },
- },
- },
- },
- expectOutputConfig: false,
- expectResponseFormat: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- adapter := NewOpenAIAdapter(OpenAIConfig{
- BaseURL: "http://localhost:4000",
- })
- req := &ChatCompletionRequest{
- Model: tt.model,
- }
- err := adapter.applyResponseFormat(req, tt.responseFormat)
- if err != nil {
- t.Fatalf("applyResponseFormat failed: %v", err)
- }
- if tt.expectOutputConfig {
- if req.OutputConfig == nil {
- t.Error("Expected OutputConfig to be set for Anthropic model")
- } else if req.OutputConfig.Format == nil {
- t.Error("Expected OutputConfig.Format to be set")
- } else if req.OutputConfig.Format.Type != "json_schema" {
- t.Errorf("Expected OutputConfig.Format.Type = json_schema, got %s", req.OutputConfig.Format.Type)
- }
- }
- if tt.expectResponseFormat {
- if req.ResponseFormat == nil {
- t.Error("Expected ResponseFormat to be set for OpenAI model")
- } else if req.ResponseFormat.Type != "json_schema" {
- t.Errorf("Expected ResponseFormat.Type = json_schema, got %s", req.ResponseFormat.Type)
- }
- }
- })
- }
- }
- // TestSchemaRefResolution tests the v3.9 schemaRef resolution feature
- func TestSchemaRefResolution(t *testing.T) {
- // Create mock LLM adapter
- mockAdapter := NewDefaultLLMAdapter()
- mockJSON := `{"projectName": "MyApp", "estimatedDays": 5}`
- mockAdapter.SetHandler(func(ctx context.Context, params map[string]interface{}, stream chan<- string) (map[string]interface{}, error) {
- // Verify that schema was resolved (not schemaRef)
- if outputConfig, ok := params["output_config"].(map[string]interface{}); ok {
- if format, ok := outputConfig["format"].(map[string]interface{}); ok {
- if _, hasSchemaRef := format["schemaRef"]; hasSchemaRef {
- t.Error("schemaRef should have been resolved before calling LLM adapter")
- }
- if _, hasSchema := format["schema"]; !hasSchema {
- t.Error("schema should be present after schemaRef resolution")
- }
- }
- }
- result := map[string]interface{}{
- "content": mockJSON,
- "model": "claude-sonnet-4-5",
- "finish_reason": "stop",
- }
- if isStructuredOutput(params) {
- var parsed interface{}
- if err := json.Unmarshal([]byte(mockJSON), &parsed); err != nil {
- return nil, err
- }
- result["content"] = parsed
- }
- return result, nil
- })
- // Create workflow with schemaRef
- wf := &Workflow{
- Version: "3.9",
- Name: "SchemaRef Test",
- Registry: Registry{
- Vars: []string{
- "$plan(OBJECT)",
- },
- Schemas: map[string]map[string]interface{}{
- "PlanSchema": {
- "type": "object",
- "properties": map[string]interface{}{
- "projectName": map[string]interface{}{
- "type": "string",
- },
- "estimatedDays": map[string]interface{}{
- "type": "integer",
- },
- },
- "required": []interface{}{"projectName", "estimatedDays"},
- "additionalProperties": false,
- },
- },
- },
- Steps: []Step{
- {
- ID: "LLM_GeneratePlan",
- In: StepInput{
- "messages": []interface{}{
- map[string]interface{}{
- "role": "user",
- "content": "Generate a project plan",
- },
- },
- "output_config": map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- "schemaRef": "PlanSchema",
- },
- },
- },
- Out: StepOutput{
- "$plan": "=_result",
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- engine, err := NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- adapters := &Adapters{
- LLM: mockAdapter,
- Service: NewDefaultServiceAdapter(),
- }
- // Execute workflow
- result, err := engine.Execute(context.Background(), nil, adapters)
- if err != nil {
- t.Fatalf("Workflow execution failed: %v", err)
- }
- // Consume events
- for range result.RunEventStream {
- }
- // Verify result
- planVar, ok := result.Context.Variables["$plan"]
- if !ok {
- t.Fatal("$plan not found in variables")
- }
- planMap, ok := planVar.(map[string]interface{})
- if !ok {
- t.Fatalf("Expected $plan to be a map, got %T", planVar)
- }
- if projectName, ok := planMap["projectName"].(string); !ok || projectName != "MyApp" {
- t.Errorf("Expected projectName to be 'MyApp', got %v", planMap["projectName"])
- }
- if estimatedDays, ok := planMap["estimatedDays"].(float64); !ok || estimatedDays != 5 {
- t.Errorf("Expected estimatedDays to be 5, got %v", planMap["estimatedDays"])
- }
- }
- // TestSchemaRefError tests error handling for invalid schemaRef
- func TestSchemaRefError(t *testing.T) {
- tests := []struct {
- name string
- schemas map[string]map[string]interface{}
- schemaRef string
- expectError string
- }{
- {
- name: "SchemaNotFound",
- schemas: map[string]map[string]interface{}{},
- schemaRef: "NonExistentSchema",
- expectError: "schema not found: NonExistentSchema",
- },
- {
- name: "BothSchemaAndSchemaRef",
- schemas: map[string]map[string]interface{}{
- "TestSchema": {
- "type": "object",
- },
- },
- schemaRef: "", // Will be set in test
- expectError: "cannot have both 'schema' and 'schemaRef'",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mockAdapter := NewDefaultLLMAdapter()
- outputConfig := map[string]interface{}{
- "format": map[string]interface{}{
- "type": "json_schema",
- },
- }
- if tt.name == "BothSchemaAndSchemaRef" {
- outputConfig["format"].(map[string]interface{})["schema"] = map[string]interface{}{"type": "object"}
- outputConfig["format"].(map[string]interface{})["schemaRef"] = "TestSchema"
- } else {
- outputConfig["format"].(map[string]interface{})["schemaRef"] = tt.schemaRef
- }
- wf := &Workflow{
- Version: "3.9",
- Name: "SchemaRef Error Test",
- Registry: Registry{
- Schemas: tt.schemas,
- },
- Steps: []Step{
- {
- ID: "LLM_Test",
- In: StepInput{
- "messages": []interface{}{},
- "output_config": outputConfig,
- },
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- }
- engine, err := NewEngine(wf)
- if err != nil {
- t.Fatalf("Failed to create engine: %v", err)
- }
- adapters := &Adapters{
- LLM: mockAdapter,
- Service: NewDefaultServiceAdapter(),
- }
- result, err := engine.Execute(context.Background(), nil, adapters)
- if err != nil {
- t.Fatalf("Engine.Execute failed: %v", err)
- }
- // Consume events and check for error
- foundError := false
- for event := range result.RunEventStream {
- if event.Type == RunEventStepError || event.Type == RunEventWorkflowFailed {
- if contains(fmt.Sprintf("%v", event.Payload), tt.expectError) {
- foundError = true
- }
- }
- }
- if !foundError {
- t.Errorf("Expected error containing %q, but no error occurred", tt.expectError)
- }
- })
- }
- }
- // TestIDEWorkflowValidation tests the v3.9 IDE workflow validation
- func TestIDEWorkflowValidation(t *testing.T) {
- tests := []struct {
- name string
- workflow *Workflow
- expectError string
- }{
- {
- name: "IDEWorkflowWithServiceNode",
- workflow: &Workflow{
- Version: "3.9",
- Name: "IDE Test",
- WorkflowType: WorkflowTypeIDE,
- Registry: Registry{},
- Steps: []Step{
- {
- ID: "Service_Test",
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- },
- expectError: "IDE workflow (WorkflowType: IDE) cannot contain Service_* nodes",
- },
- {
- name: "IDEWorkflowWithServicesRegistry",
- workflow: &Workflow{
- Version: "3.9",
- Name: "IDE Test",
- WorkflowType: WorkflowTypeIDE,
- Registry: Registry{
- Services: []string{
- "TestService() RETURN result(STRING)",
- },
- },
- Steps: []Step{
- {
- ID: "LLM_Test",
- In: StepInput{},
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- },
- expectError: "IDE workflow (WorkflowType: IDE) must have empty registry.services",
- },
- {
- name: "IDEWorkflowValid",
- workflow: &Workflow{
- Version: "3.9",
- Name: "IDE Test",
- WorkflowType: WorkflowTypeIDE,
- Registry: Registry{},
- Steps: []Step{
- {
- ID: "LLM_Test",
- In: StepInput{},
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- },
- expectError: "",
- },
- {
- name: "BusinessWorkflowWithServiceNode",
- workflow: &Workflow{
- Version: "3.9",
- Name: "Business Test",
- WorkflowType: WorkflowTypeBusiness,
- Registry: Registry{
- Services: []string{
- "TestService() RETURN result(STRING)",
- },
- },
- Steps: []Step{
- {
- ID: "Service_Test",
- Next: "Stop_End",
- },
- {
- ID: "Stop_End",
- },
- },
- },
- expectError: "",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := NewEngine(tt.workflow)
- if tt.expectError == "" {
- if err != nil {
- t.Errorf("Expected no error, got: %v", err)
- }
- } else {
- if err == nil {
- t.Errorf("Expected error containing %q, got no error", tt.expectError)
- } else if !contains(err.Error(), tt.expectError) {
- t.Errorf("Expected error containing %q, got: %v", tt.expectError, err)
- }
- }
- })
- }
- }
|