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") } }