| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- package main
- import (
- "context"
- "encoding/json"
- "os"
- "strings"
- "testing"
- "time"
- "gogs.dmsc.dev/arp/arp_cli/cmd"
- "gogs.dmsc.dev/arp/arp_cli/config"
- "github.com/urfave/cli/v3"
- )
- // Integration tests for arp_cli workflow commands
- // These tests require a running ARP server and test the full CLI stack
- var (
- testServerURL string
- testToken string
- testUserID string
- )
- // TestMain sets up the integration test environment
- func TestMain(m *testing.M) {
- // Check for test server URL
- testServerURL = os.Getenv("ARP_TEST_URL")
- if testServerURL == "" {
- testServerURL = "http://localhost:8080/query"
- }
- // Run tests
- code := m.Run()
- os.Exit(code)
- }
- // setupTestAuth creates a test configuration with authentication
- func setupTestAuth(t *testing.T) {
- t.Helper()
- // Create test config
- cfg := &config.Config{
- ServerURL: testServerURL,
- Token: testToken,
- }
- if err := config.Save(cfg); err != nil {
- t.Fatalf("Failed to save test config: %v", err)
- }
- }
- // TestIntegration_WorkflowTemplateLifecycle tests the full lifecycle of workflow templates
- func TestIntegration_WorkflowTemplateLifecycle(t *testing.T) {
- if testing.Short() {
- t.Skip("Skipping integration test in short mode")
- }
- // This test would require a running server with authentication
- // For now, we'll test the command structure and flag parsing
- app := buildTestApp()
- // Test 1: List templates (should work even with no auth for structure test)
- t.Run("TemplateList", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "template", "list", "--json"}
- err := app.Run(context.Background(), args)
- // We expect an error about missing auth, but the command structure should be valid
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- // Test 2: Get template with missing ID
- t.Run("TemplateGetMissingID", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "template", "get"}
- err := app.Run(context.Background(), args)
- if err == nil {
- t.Error("Expected error for missing required flag")
- }
- })
- // Test 3: Create template with flags
- t.Run("TemplateCreateFlags", func(t *testing.T) {
- args := []string{
- "arp_cli", "workflow", "template", "create",
- "--name", "Test Template",
- "--description", "A test workflow template",
- "--definition", `{"nodes": [], "edges": []}`,
- }
- err := app.Run(context.Background(), args)
- // We expect an error about missing auth
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- }
- // TestIntegration_WorkflowInstanceLifecycle tests the full lifecycle of workflow instances
- func TestIntegration_WorkflowInstanceLifecycle(t *testing.T) {
- if testing.Short() {
- t.Skip("Skipping integration test in short mode")
- }
- app := buildTestApp()
- // Test 1: List instances
- t.Run("InstanceList", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "instance", "list", "--json"}
- err := app.Run(context.Background(), args)
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- // Test 2: Start instance with missing template
- t.Run("InstanceStartMissingTemplate", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "instance", "start"}
- err := app.Run(context.Background(), args)
- if err == nil {
- t.Error("Expected error for missing required flag")
- }
- })
- // Test 3: Cancel instance with missing ID
- t.Run("InstanceCancelMissingID", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "instance", "cancel"}
- err := app.Run(context.Background(), args)
- if err == nil {
- t.Error("Expected error for missing required flag")
- }
- })
- }
- // TestIntegration_WorkflowNodeLifecycle tests the full lifecycle of workflow nodes
- func TestIntegration_WorkflowNodeLifecycle(t *testing.T) {
- if testing.Short() {
- t.Skip("Skipping integration test in short mode")
- }
- app := buildTestApp()
- // Test 1: List nodes with missing instance
- t.Run("NodeListMissingInstance", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "node", "list"}
- err := app.Run(context.Background(), args)
- if err == nil {
- t.Error("Expected error for missing required flag")
- }
- })
- // Test 2: Get node with missing ID
- t.Run("NodeGetMissingID", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "node", "get"}
- err := app.Run(context.Background(), args)
- if err == nil {
- t.Error("Expected error for missing required flag")
- }
- })
- // Test 3: Retry node with missing ID
- t.Run("NodeRetryMissingID", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "node", "retry"}
- err := app.Run(context.Background(), args)
- if err == nil {
- t.Error("Expected error for missing required flag")
- }
- })
- }
- // TestIntegration_CommandHelp tests that help is available for all workflow commands
- func TestIntegration_CommandHelp(t *testing.T) {
- app := buildTestApp()
- tests := []struct {
- name string
- args []string
- }{
- {"workflow help", []string{"arp_cli", "workflow", "--help"}},
- {"template help", []string{"arp_cli", "workflow", "template", "--help"}},
- {"instance help", []string{"arp_cli", "workflow", "instance", "--help"}},
- {"node help", []string{"arp_cli", "workflow", "node", "--help"}},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := app.Run(context.Background(), tt.args)
- if err != nil {
- t.Errorf("Help command failed: %v", err)
- }
- })
- }
- }
- // TestIntegration_JSONOutput tests JSON output format
- func TestIntegration_JSONOutput(t *testing.T) {
- if testing.Short() {
- t.Skip("Skipping integration test in short mode")
- }
- app := buildTestApp()
- // Test that JSON flag is accepted
- t.Run("TemplateListJSON", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "template", "list", "--json"}
- err := app.Run(context.Background(), args)
- // We expect an error about missing auth
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- t.Run("InstanceListJSON", func(t *testing.T) {
- args := []string{"arp_cli", "workflow", "instance", "list", "--json"}
- err := app.Run(context.Background(), args)
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- }
- // TestIntegration_FileInput tests file input for workflow definitions
- func TestIntegration_FileInput(t *testing.T) {
- // Create a temporary file with workflow definition
- tmpFile, err := os.CreateTemp("", "workflow_*.json")
- if err != nil {
- t.Fatalf("Failed to create temp file: %v", err)
- }
- defer os.Remove(tmpFile.Name())
- definition := `{
- "nodes": [
- {"id": "start", "type": "start"},
- {"id": "task1", "type": "task"},
- {"id": "end", "type": "end"}
- ],
- "edges": [
- {"from": "start", "to": "task1"},
- {"from": "task1", "to": "end"}
- ]
- }`
- if _, err := tmpFile.WriteString(definition); err != nil {
- t.Fatalf("Failed to write to temp file: %v", err)
- }
- tmpFile.Close()
- // Test that readFileOrString works correctly
- result := cmd.ReadFileOrString("@" + tmpFile.Name())
- if result != definition {
- t.Errorf("File content mismatch.\nExpected: %s\nGot: %s", definition, result)
- }
- }
- // TestIntegration_CommandAliases tests command aliases
- func TestIntegration_CommandAliases(t *testing.T) {
- app := buildTestApp()
- tests := []struct {
- name string
- args []string
- }{
- {"template list alias", []string{"arp_cli", "workflow", "template", "ls"}},
- {"instance list alias", []string{"arp_cli", "workflow", "instance", "ls"}},
- {"node list alias", []string{"arp_cli", "workflow", "node", "ls"}},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := app.Run(context.Background(), tt.args)
- // We expect an error about missing auth
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- }
- }
- // TestIntegration_MultipleFlags tests commands with multiple flags
- func TestIntegration_MultipleFlags(t *testing.T) {
- if testing.Short() {
- t.Skip("Skipping integration test in short mode")
- }
- app := buildTestApp()
- t.Run("StartWithAllFlags", func(t *testing.T) {
- args := []string{
- "arp_cli", "workflow", "instance", "start",
- "--template", "template-123",
- "--service", "service-456",
- "--context", `{"key": "value"}`,
- }
- err := app.Run(context.Background(), args)
- // We expect an error about missing auth
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- t.Run("CreateWithAllFlags", func(t *testing.T) {
- args := []string{
- "arp_cli", "workflow", "template", "create",
- "--name", "Test Workflow",
- "--description", "Test Description",
- "--definition", `{"nodes": []}`,
- "--active",
- }
- err := app.Run(context.Background(), args)
- if err != nil && !strings.Contains(err.Error(), "not authenticated") {
- t.Logf("Expected auth error or success, got: %v", err)
- }
- })
- }
- // TestIntegration_ErrorHandling tests error handling for required flags
- func TestIntegration_ErrorHandling(t *testing.T) {
- app := buildTestApp()
- tests := []struct {
- name string
- args []string
- expectError bool
- }{
- {"missing template id", []string{"arp_cli", "workflow", "template", "get"}, true},
- {"missing instance id", []string{"arp_cli", "workflow", "instance", "get"}, true},
- {"missing node id", []string{"arp_cli", "workflow", "node", "get"}, true},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := app.Run(context.Background(), tt.args)
- hasError := err != nil
- if hasError != tt.expectError {
- t.Errorf("Expected error=%v, got error=%v (%v)", tt.expectError, hasError, err)
- }
- })
- }
- }
- // TestIntegration_WorkflowDefinitionValidation tests workflow definition handling
- func TestIntegration_WorkflowDefinitionValidation(t *testing.T) {
- // Test various workflow definition formats
- tests := []struct {
- name string
- definition string
- valid bool
- }{
- {
- name: "empty definition",
- definition: `{}`,
- valid: true,
- },
- {
- name: "simple DAG",
- definition: `{"nodes": [{"id": "a"}], "edges": []}`,
- valid: true,
- },
- {
- name: "complex DAG",
- definition: `{"nodes": [{"id": "start", "type": "start"}, {"id": "end", "type": "end"}], "edges": [{"from": "start", "to": "end"}]}`,
- valid: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Verify the definition is valid JSON
- var data interface{}
- err := json.Unmarshal([]byte(tt.definition), &data)
- if tt.valid && err != nil {
- t.Errorf("Expected valid JSON, got error: %v", err)
- }
- if !tt.valid && err == nil {
- t.Error("Expected invalid JSON, but parsing succeeded")
- }
- })
- }
- }
- // buildTestApp creates a CLI app for testing
- func buildTestApp() *cli.Command {
- return &cli.Command{
- Name: "arp_cli",
- Usage: "Test CLI",
- Commands: []*cli.Command{cmd.WorkflowCommand()},
- }
- }
- // Helper function to check if a string contains a substring
- func containsString(s, substr string) bool {
- return strings.Contains(s, substr)
- }
- // BenchmarkWorkflowCommand benchmarks the workflow command execution
- func BenchmarkWorkflowCommand(b *testing.B) {
- app := buildTestApp()
- args := []string{"arp_cli", "workflow", "--help"}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _ = app.Run(context.Background(), args)
- }
- }
- // BenchmarkWorkflowTemplateList benchmarks the template list command
- func BenchmarkWorkflowTemplateList(b *testing.B) {
- app := buildTestApp()
- args := []string{"arp_cli", "workflow", "template", "list", "--json"}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _ = app.Run(context.Background(), args)
- }
- }
- // Mock test for context handling
- func TestContextHandling(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- app := buildTestApp()
- args := []string{"arp_cli", "workflow", "template", "list"}
- // The command should respect context cancellation
- done := make(chan error, 1)
- go func() {
- done <- app.Run(ctx, args)
- }()
- select {
- case <-ctx.Done():
- t.Log("Context cancelled")
- case err := <-done:
- if err != nil {
- t.Logf("Command completed with error: %v", err)
- }
- case <-time.After(10 * time.Second):
- t.Error("Command took too long")
- }
- }
|