integration_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "os"
  6. "strings"
  7. "testing"
  8. "time"
  9. "gogs.dmsc.dev/arp/arp_cli/cmd"
  10. "gogs.dmsc.dev/arp/arp_cli/config"
  11. "github.com/urfave/cli/v3"
  12. )
  13. // Integration tests for arp_cli workflow commands
  14. // These tests require a running ARP server and test the full CLI stack
  15. var (
  16. testServerURL string
  17. testToken string
  18. testUserID string
  19. )
  20. // TestMain sets up the integration test environment
  21. func TestMain(m *testing.M) {
  22. // Check for test server URL
  23. testServerURL = os.Getenv("ARP_TEST_URL")
  24. if testServerURL == "" {
  25. testServerURL = "http://localhost:8080/query"
  26. }
  27. // Run tests
  28. code := m.Run()
  29. os.Exit(code)
  30. }
  31. // setupTestAuth creates a test configuration with authentication
  32. func setupTestAuth(t *testing.T) {
  33. t.Helper()
  34. // Create test config
  35. cfg := &config.Config{
  36. ServerURL: testServerURL,
  37. Token: testToken,
  38. }
  39. if err := config.Save(cfg); err != nil {
  40. t.Fatalf("Failed to save test config: %v", err)
  41. }
  42. }
  43. // TestIntegration_WorkflowTemplateLifecycle tests the full lifecycle of workflow templates
  44. func TestIntegration_WorkflowTemplateLifecycle(t *testing.T) {
  45. if testing.Short() {
  46. t.Skip("Skipping integration test in short mode")
  47. }
  48. // This test would require a running server with authentication
  49. // For now, we'll test the command structure and flag parsing
  50. app := buildTestApp()
  51. // Test 1: List templates (should work even with no auth for structure test)
  52. t.Run("TemplateList", func(t *testing.T) {
  53. args := []string{"arp_cli", "workflow", "template", "list", "--json"}
  54. err := app.Run(context.Background(), args)
  55. // We expect an error about missing auth, but the command structure should be valid
  56. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  57. t.Logf("Expected auth error or success, got: %v", err)
  58. }
  59. })
  60. // Test 2: Get template with missing ID
  61. t.Run("TemplateGetMissingID", func(t *testing.T) {
  62. args := []string{"arp_cli", "workflow", "template", "get"}
  63. err := app.Run(context.Background(), args)
  64. if err == nil {
  65. t.Error("Expected error for missing required flag")
  66. }
  67. })
  68. // Test 3: Create template with flags
  69. t.Run("TemplateCreateFlags", func(t *testing.T) {
  70. args := []string{
  71. "arp_cli", "workflow", "template", "create",
  72. "--name", "Test Template",
  73. "--description", "A test workflow template",
  74. "--definition", `{"nodes": [], "edges": []}`,
  75. }
  76. err := app.Run(context.Background(), args)
  77. // We expect an error about missing auth
  78. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  79. t.Logf("Expected auth error or success, got: %v", err)
  80. }
  81. })
  82. }
  83. // TestIntegration_WorkflowInstanceLifecycle tests the full lifecycle of workflow instances
  84. func TestIntegration_WorkflowInstanceLifecycle(t *testing.T) {
  85. if testing.Short() {
  86. t.Skip("Skipping integration test in short mode")
  87. }
  88. app := buildTestApp()
  89. // Test 1: List instances
  90. t.Run("InstanceList", func(t *testing.T) {
  91. args := []string{"arp_cli", "workflow", "instance", "list", "--json"}
  92. err := app.Run(context.Background(), args)
  93. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  94. t.Logf("Expected auth error or success, got: %v", err)
  95. }
  96. })
  97. // Test 2: Start instance with missing template
  98. t.Run("InstanceStartMissingTemplate", func(t *testing.T) {
  99. args := []string{"arp_cli", "workflow", "instance", "start"}
  100. err := app.Run(context.Background(), args)
  101. if err == nil {
  102. t.Error("Expected error for missing required flag")
  103. }
  104. })
  105. // Test 3: Cancel instance with missing ID
  106. t.Run("InstanceCancelMissingID", func(t *testing.T) {
  107. args := []string{"arp_cli", "workflow", "instance", "cancel"}
  108. err := app.Run(context.Background(), args)
  109. if err == nil {
  110. t.Error("Expected error for missing required flag")
  111. }
  112. })
  113. }
  114. // TestIntegration_WorkflowNodeLifecycle tests the full lifecycle of workflow nodes
  115. func TestIntegration_WorkflowNodeLifecycle(t *testing.T) {
  116. if testing.Short() {
  117. t.Skip("Skipping integration test in short mode")
  118. }
  119. app := buildTestApp()
  120. // Test 1: List nodes with missing instance
  121. t.Run("NodeListMissingInstance", func(t *testing.T) {
  122. args := []string{"arp_cli", "workflow", "node", "list"}
  123. err := app.Run(context.Background(), args)
  124. if err == nil {
  125. t.Error("Expected error for missing required flag")
  126. }
  127. })
  128. // Test 2: Get node with missing ID
  129. t.Run("NodeGetMissingID", func(t *testing.T) {
  130. args := []string{"arp_cli", "workflow", "node", "get"}
  131. err := app.Run(context.Background(), args)
  132. if err == nil {
  133. t.Error("Expected error for missing required flag")
  134. }
  135. })
  136. // Test 3: Retry node with missing ID
  137. t.Run("NodeRetryMissingID", func(t *testing.T) {
  138. args := []string{"arp_cli", "workflow", "node", "retry"}
  139. err := app.Run(context.Background(), args)
  140. if err == nil {
  141. t.Error("Expected error for missing required flag")
  142. }
  143. })
  144. }
  145. // TestIntegration_CommandHelp tests that help is available for all workflow commands
  146. func TestIntegration_CommandHelp(t *testing.T) {
  147. app := buildTestApp()
  148. tests := []struct {
  149. name string
  150. args []string
  151. }{
  152. {"workflow help", []string{"arp_cli", "workflow", "--help"}},
  153. {"template help", []string{"arp_cli", "workflow", "template", "--help"}},
  154. {"instance help", []string{"arp_cli", "workflow", "instance", "--help"}},
  155. {"node help", []string{"arp_cli", "workflow", "node", "--help"}},
  156. }
  157. for _, tt := range tests {
  158. t.Run(tt.name, func(t *testing.T) {
  159. err := app.Run(context.Background(), tt.args)
  160. if err != nil {
  161. t.Errorf("Help command failed: %v", err)
  162. }
  163. })
  164. }
  165. }
  166. // TestIntegration_JSONOutput tests JSON output format
  167. func TestIntegration_JSONOutput(t *testing.T) {
  168. if testing.Short() {
  169. t.Skip("Skipping integration test in short mode")
  170. }
  171. app := buildTestApp()
  172. // Test that JSON flag is accepted
  173. t.Run("TemplateListJSON", func(t *testing.T) {
  174. args := []string{"arp_cli", "workflow", "template", "list", "--json"}
  175. err := app.Run(context.Background(), args)
  176. // We expect an error about missing auth
  177. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  178. t.Logf("Expected auth error or success, got: %v", err)
  179. }
  180. })
  181. t.Run("InstanceListJSON", func(t *testing.T) {
  182. args := []string{"arp_cli", "workflow", "instance", "list", "--json"}
  183. err := app.Run(context.Background(), args)
  184. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  185. t.Logf("Expected auth error or success, got: %v", err)
  186. }
  187. })
  188. }
  189. // TestIntegration_FileInput tests file input for workflow definitions
  190. func TestIntegration_FileInput(t *testing.T) {
  191. // Create a temporary file with workflow definition
  192. tmpFile, err := os.CreateTemp("", "workflow_*.json")
  193. if err != nil {
  194. t.Fatalf("Failed to create temp file: %v", err)
  195. }
  196. defer os.Remove(tmpFile.Name())
  197. definition := `{
  198. "nodes": [
  199. {"id": "start", "type": "start"},
  200. {"id": "task1", "type": "task"},
  201. {"id": "end", "type": "end"}
  202. ],
  203. "edges": [
  204. {"from": "start", "to": "task1"},
  205. {"from": "task1", "to": "end"}
  206. ]
  207. }`
  208. if _, err := tmpFile.WriteString(definition); err != nil {
  209. t.Fatalf("Failed to write to temp file: %v", err)
  210. }
  211. tmpFile.Close()
  212. // Test that readFileOrString works correctly
  213. result := cmd.ReadFileOrString("@" + tmpFile.Name())
  214. if result != definition {
  215. t.Errorf("File content mismatch.\nExpected: %s\nGot: %s", definition, result)
  216. }
  217. }
  218. // TestIntegration_CommandAliases tests command aliases
  219. func TestIntegration_CommandAliases(t *testing.T) {
  220. app := buildTestApp()
  221. tests := []struct {
  222. name string
  223. args []string
  224. }{
  225. {"template list alias", []string{"arp_cli", "workflow", "template", "ls"}},
  226. {"instance list alias", []string{"arp_cli", "workflow", "instance", "ls"}},
  227. {"node list alias", []string{"arp_cli", "workflow", "node", "ls"}},
  228. }
  229. for _, tt := range tests {
  230. t.Run(tt.name, func(t *testing.T) {
  231. err := app.Run(context.Background(), tt.args)
  232. // We expect an error about missing auth
  233. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  234. t.Logf("Expected auth error or success, got: %v", err)
  235. }
  236. })
  237. }
  238. }
  239. // TestIntegration_MultipleFlags tests commands with multiple flags
  240. func TestIntegration_MultipleFlags(t *testing.T) {
  241. if testing.Short() {
  242. t.Skip("Skipping integration test in short mode")
  243. }
  244. app := buildTestApp()
  245. t.Run("StartWithAllFlags", func(t *testing.T) {
  246. args := []string{
  247. "arp_cli", "workflow", "instance", "start",
  248. "--template", "template-123",
  249. "--service", "service-456",
  250. "--context", `{"key": "value"}`,
  251. }
  252. err := app.Run(context.Background(), args)
  253. // We expect an error about missing auth
  254. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  255. t.Logf("Expected auth error or success, got: %v", err)
  256. }
  257. })
  258. t.Run("CreateWithAllFlags", func(t *testing.T) {
  259. args := []string{
  260. "arp_cli", "workflow", "template", "create",
  261. "--name", "Test Workflow",
  262. "--description", "Test Description",
  263. "--definition", `{"nodes": []}`,
  264. "--active",
  265. }
  266. err := app.Run(context.Background(), args)
  267. if err != nil && !strings.Contains(err.Error(), "not authenticated") {
  268. t.Logf("Expected auth error or success, got: %v", err)
  269. }
  270. })
  271. }
  272. // TestIntegration_ErrorHandling tests error handling for required flags
  273. func TestIntegration_ErrorHandling(t *testing.T) {
  274. app := buildTestApp()
  275. tests := []struct {
  276. name string
  277. args []string
  278. expectError bool
  279. }{
  280. {"missing template id", []string{"arp_cli", "workflow", "template", "get"}, true},
  281. {"missing instance id", []string{"arp_cli", "workflow", "instance", "get"}, true},
  282. {"missing node id", []string{"arp_cli", "workflow", "node", "get"}, true},
  283. }
  284. for _, tt := range tests {
  285. t.Run(tt.name, func(t *testing.T) {
  286. err := app.Run(context.Background(), tt.args)
  287. hasError := err != nil
  288. if hasError != tt.expectError {
  289. t.Errorf("Expected error=%v, got error=%v (%v)", tt.expectError, hasError, err)
  290. }
  291. })
  292. }
  293. }
  294. // TestIntegration_WorkflowDefinitionValidation tests workflow definition handling
  295. func TestIntegration_WorkflowDefinitionValidation(t *testing.T) {
  296. // Test various workflow definition formats
  297. tests := []struct {
  298. name string
  299. definition string
  300. valid bool
  301. }{
  302. {
  303. name: "empty definition",
  304. definition: `{}`,
  305. valid: true,
  306. },
  307. {
  308. name: "simple DAG",
  309. definition: `{"nodes": [{"id": "a"}], "edges": []}`,
  310. valid: true,
  311. },
  312. {
  313. name: "complex DAG",
  314. definition: `{"nodes": [{"id": "start", "type": "start"}, {"id": "end", "type": "end"}], "edges": [{"from": "start", "to": "end"}]}`,
  315. valid: true,
  316. },
  317. }
  318. for _, tt := range tests {
  319. t.Run(tt.name, func(t *testing.T) {
  320. // Verify the definition is valid JSON
  321. var data interface{}
  322. err := json.Unmarshal([]byte(tt.definition), &data)
  323. if tt.valid && err != nil {
  324. t.Errorf("Expected valid JSON, got error: %v", err)
  325. }
  326. if !tt.valid && err == nil {
  327. t.Error("Expected invalid JSON, but parsing succeeded")
  328. }
  329. })
  330. }
  331. }
  332. // buildTestApp creates a CLI app for testing
  333. func buildTestApp() *cli.Command {
  334. return &cli.Command{
  335. Name: "arp_cli",
  336. Usage: "Test CLI",
  337. Commands: []*cli.Command{cmd.WorkflowCommand()},
  338. }
  339. }
  340. // Helper function to check if a string contains a substring
  341. func containsString(s, substr string) bool {
  342. return strings.Contains(s, substr)
  343. }
  344. // BenchmarkWorkflowCommand benchmarks the workflow command execution
  345. func BenchmarkWorkflowCommand(b *testing.B) {
  346. app := buildTestApp()
  347. args := []string{"arp_cli", "workflow", "--help"}
  348. b.ResetTimer()
  349. for i := 0; i < b.N; i++ {
  350. _ = app.Run(context.Background(), args)
  351. }
  352. }
  353. // BenchmarkWorkflowTemplateList benchmarks the template list command
  354. func BenchmarkWorkflowTemplateList(b *testing.B) {
  355. app := buildTestApp()
  356. args := []string{"arp_cli", "workflow", "template", "list", "--json"}
  357. b.ResetTimer()
  358. for i := 0; i < b.N; i++ {
  359. _ = app.Run(context.Background(), args)
  360. }
  361. }
  362. // Mock test for context handling
  363. func TestContextHandling(t *testing.T) {
  364. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  365. defer cancel()
  366. app := buildTestApp()
  367. args := []string{"arp_cli", "workflow", "template", "list"}
  368. // The command should respect context cancellation
  369. done := make(chan error, 1)
  370. go func() {
  371. done <- app.Run(ctx, args)
  372. }()
  373. select {
  374. case <-ctx.Done():
  375. t.Log("Context cancelled")
  376. case err := <-done:
  377. if err != nil {
  378. t.Logf("Command completed with error: %v", err)
  379. }
  380. case <-time.After(10 * time.Second):
  381. t.Error("Command took too long")
  382. }
  383. }