Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ jobs:
path: '*/*/dist/go-test.json'
retention-days: 1

cli-integration:
name: CLI Integration Tests
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4

- name: Setup runner environment
uses: ./.github/actions/setup

- name: Run CLI integration tests
run: pnpm nx run cli:test:integration

go-test-summary:
name: Test
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ storybook-static
tmp/
tsconfig.tsbuildinfo
tsp-output
.bench/
.ralph/
.ralphrc

# Coverage files
*.out
Expand Down
15 changes: 13 additions & 2 deletions apps/cli/cmd/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/the-dev-tools/dev-tools/packages/db/pkg/sqlitemem"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/expression"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/flow/flowbuilder"
gqlresolver "github.com/the-dev-tools/dev-tools/packages/server/pkg/graphql/resolver"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/http/resolver"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/idwrap"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/ioworkspace"
Expand Down Expand Up @@ -153,7 +154,7 @@ var yamlflowRunCmd = &cobra.Command{
return fmt.Errorf("failed to convert YAML using v2: %w", err)
}

resolver := resolver.NewStandardResolver(
httpResolver := resolver.NewStandardResolver(
&services.HTTP,
&services.HTTPHeader,
services.HTTPSearchParam,
Expand All @@ -163,6 +164,12 @@ var yamlflowRunCmd = &cobra.Command{
services.HTTPAssert,
)

graphqlResolver := gqlresolver.NewStandardResolver(
services.GraphQL.Reader(),
&services.GraphQLHeader,
&services.GraphQLAssert,
)

// Create LLM provider factory for AI nodes
llmFactory := scredential.NewLLMProviderFactory(&services.Credential)

Expand All @@ -176,10 +183,14 @@ var yamlflowRunCmd = &cobra.Command{
&services.NodeAI,
&services.NodeAiProvider,
&services.NodeMemory,
&services.NodeGraphQL,
&services.GraphQL,
&services.GraphQLHeader,
&services.Workspace,
&services.Variable,
&services.FlowVariable,
resolver,
httpResolver,
graphqlResolver,
services.Logger,
llmFactory,
)
Expand Down
13 changes: 13 additions & 0 deletions apps/cli/internal/common/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/scredential"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/senv"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sflow"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sgraphql"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/shttp"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sworkspace"
)
Expand Down Expand Up @@ -40,6 +41,12 @@ type Services struct {
NodeAI sflow.NodeAIService
NodeAiProvider sflow.NodeAiProviderService
NodeMemory sflow.NodeMemoryService
NodeGraphQL sflow.NodeGraphQLService

// GraphQL
GraphQL sgraphql.GraphQLService
GraphQLHeader sgraphql.GraphQLHeaderService
GraphQLAssert sgraphql.GraphQLAssertService

// Credentials
Credential scredential.CredentialService
Expand Down Expand Up @@ -87,6 +94,12 @@ func CreateServices(ctx context.Context, db *sql.DB, logger *slog.Logger) (*Serv
NodeAI: sflow.NewNodeAIService(queries),
NodeAiProvider: sflow.NewNodeAiProviderService(queries),
NodeMemory: sflow.NewNodeMemoryService(queries),
NodeGraphQL: sflow.NewNodeGraphQLService(queries),

// GraphQL
GraphQL: sgraphql.New(queries, logger),
GraphQLHeader: sgraphql.NewGraphQLHeaderService(queries),
GraphQLAssert: sgraphql.NewGraphQLAssertService(queries),

// Credentials
Credential: scredential.NewCredentialService(queries),
Expand Down
13 changes: 13 additions & 0 deletions apps/cli/internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/the-dev-tools/dev-tools/apps/cli/internal/reporter"

"github.com/the-dev-tools/dev-tools/packages/server/pkg/flow/node"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/flow/node/ngraphql"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/flow/node/nrequest"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/flow/runner"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/flow/runner/flowlocalrunner"
Expand Down Expand Up @@ -251,6 +252,17 @@ func RunFlow(ctx context.Context, flowPtr *mflow.Flow, services RunnerServices,
}()
defer close(requestRespChan)

// Initialize GraphQL response channel
gqlRespChan := make(chan ngraphql.NodeGraphQLSideResp, requestBufferSize)
go func() {
for resp := range gqlRespChan {
if resp.Done != nil {
close(resp.Done)
}
}
}()
defer close(gqlRespChan)

// Build flow node map using flowbuilder
flowNodeMap, startNodeID, err := services.Builder.BuildNodes(
ctx,
Expand All @@ -259,6 +271,7 @@ func RunFlow(ctx context.Context, flowPtr *mflow.Flow, services RunnerServices,
nodeTimeout,
httpClient,
requestRespChan,
gqlRespChan,
services.JSClient,
)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions apps/cli/internal/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,14 @@ func newFlowTestFixture(t *testing.T) *flowTestFixture {
nil, // NodeAIService - not needed for CLI tests
nil, // NodeAiProviderService - not needed for CLI tests
nil, // NodeMemoryService - not needed for CLI tests
nil, // NodeGraphQLService - not needed for CLI tests
nil, // GraphQLService - not needed for CLI tests
nil, // GraphQLHeaderService - not needed for CLI tests
&workspaceService,
&varService,
&flowVariableService,
res,
nil, // GraphQLResolver - not needed for CLI tests
logger,
nil, // LLMProviderFactory - not needed for CLI tests
)
Expand Down
15 changes: 15 additions & 0 deletions apps/cli/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@
},
"dependsOn": ["copy-worker"]
},
"test:integration": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"parallel": false,
"commands": [
"go build -tags cli -o dist/devtools-cli-test .",
"RUN_CLI_INTEGRATION=true DEVTOOLS_CLI_BIN=$(pwd)/dist/devtools-cli-test go test -tags cli_integration ./test/yamlflow/ -v -timeout 120s"
],
"env": {
"DEVTOOLS_MODE": "cli"
}
},
"dependsOn": ["copy-worker"]
},
"lint": {
"executor": "nx:run-commands",
"options": {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/test/yamlflow/example_run_yamlflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ flows:
depends_on: RequestB
- if:
name: CheckPostCount
condition: RequestB.response.body.length > 10
condition: len(RequestB.response.body) > 10
then: LogManyPosts
else: LogFewPosts
depends_on: RequestB
Expand Down
70 changes: 70 additions & 0 deletions apps/cli/test/yamlflow/graphql_run_example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
workspace_name: GraphQL Run Example
run:
- flow: QueryAndLookup
graphql_requests:
- name: ListCountries
url: https://countries.trevorblades.com/graphql
query: |-
query {
countries {
code
name
capital
}
}
variables: '{}'
assertions:
- response.status == 200
- response.body.data.countries != nil
- name: GetCountry
url: https://countries.trevorblades.com/graphql
query: |-
query GetCountry($code: ID!) {
country(code: $code) {
name
capital
currency
languages {
name
}
}
}
variables: '{}'
assertions:
- response.status == 200
flows:
- name: QueryAndLookup
steps:
- manual_start:
name: Start
position_x: 0
position_y: 0
- graphql:
name: ListCountries
depends_on: Start
position_x: 300
position_y: 0
use_request: ListCountries
- js:
name: PickCountry
depends_on: ListCountries
position_x: 600
position_y: 0
code: |-
export default function(ctx) {
const countries = ctx.ListCountries.response.body.data.countries;
const country = countries[0];
return { code: country.code, name: country.name };
}
- graphql:
name: GetCountry
depends_on: PickCountry
position_x: 900
position_y: 0
use_request: GetCountry
variables: '{"code": "{{PickCountry.code}}"}'
assertions:
- response.body.data.country.name != nil
environments:
- name: default
variables: {}
78 changes: 78 additions & 0 deletions apps/cli/test/yamlflow/integration_yamlflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//go:build cli_integration

package yamlflow_test

import (
"os"
"os/exec"
"path/filepath"
"testing"
)

// TestMain builds the CLI binary once for all tests in this package.
// If DEVTOOLS_CLI_BIN is already set, it skips the build step.
func TestMain(m *testing.M) {
if os.Getenv("RUN_CLI_INTEGRATION") != "true" {
os.Exit(0)
}

binPath := os.Getenv("DEVTOOLS_CLI_BIN")
cleanUp := false
if binPath == "" {
// Build CLI binary with cli tag
binPath = filepath.Join(os.TempDir(), "devtools-cli-test")
cmd := exec.Command("go", "build", "-tags", "cli", "-o", binPath, "../../.")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic("failed to build CLI binary: " + err.Error())
}
os.Setenv("DEVTOOLS_CLI_BIN", binPath)
cleanUp = true
}

code := m.Run()

if cleanUp {
os.Remove(binPath)
}
os.Exit(code)
}

func runCLI(t *testing.T, yamlFile string) {
t.Helper()

binPath := os.Getenv("DEVTOOLS_CLI_BIN")
if binPath == "" {
t.Fatal("DEVTOOLS_CLI_BIN not set")
}

cmd := exec.Command(binPath, "flow", "run", yamlFile)
cmd.Env = append(os.Environ(), "DEVTOOLS_MODE=cli")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI failed for %s:\n%s", filepath.Base(yamlFile), string(out))
}

t.Logf("CLI output for %s:\n%s", filepath.Base(yamlFile), string(out))
}

func TestYAMLFlow_SimpleRun(t *testing.T) {
runCLI(t, "simple_run_example.yaml")
}

func TestYAMLFlow_MultiFlowRun(t *testing.T) {
runCLI(t, "multi_flow_run_example.yaml")
}

func TestYAMLFlow_ExampleRun(t *testing.T) {
runCLI(t, "example_run_yamlflow.yaml")
}

func TestYAMLFlow_TestRunField(t *testing.T) {
runCLI(t, "test_run_field.yaml")
}

func TestYAMLFlow_GraphQLRun(t *testing.T) {
runCLI(t, "graphql_run_example.yaml")
}
Loading