278 lines
8.3 KiB
Go
278 lines
8.3 KiB
Go
// Package integration provides end-to-end integration tests for the Kandev backend.
|
|
package integration
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
ws "github.com/kandev/kandev/pkg/websocket"
|
|
)
|
|
|
|
func TestMultipleClients(t *testing.T) {
|
|
ts := NewTestServer(t)
|
|
defer ts.Close()
|
|
|
|
client1 := NewWSClient(t, ts.Server.URL)
|
|
defer client1.Close()
|
|
|
|
client2 := NewWSClient(t, ts.Server.URL)
|
|
defer client2.Close()
|
|
|
|
workspaceID := createWorkspace(t, client1)
|
|
repositoryID := createRepository(t, client1, workspaceID)
|
|
|
|
// Client 1 creates a workflow
|
|
workflowResp, err := client1.SendRequest("c1-workflow-1", ws.ActionWorkflowCreate, map[string]interface{}{
|
|
"workspace_id": workspaceID,
|
|
"name": "Shared Workflow",
|
|
"workflow_template_id": "simple",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var workflowPayload map[string]interface{}
|
|
err = workflowResp.ParsePayload(&workflowPayload)
|
|
require.NoError(t, err)
|
|
workflowID := workflowPayload["id"].(string)
|
|
|
|
// Client 2 can see both the workspace's default Kanban workflow and the
|
|
// additional workflow created by client 1.
|
|
listResp, err := client2.SendRequest("c2-list-1", ws.ActionWorkflowList, map[string]interface{}{
|
|
"workspace_id": workspaceID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var listPayload map[string]interface{}
|
|
err = listResp.ParsePayload(&listPayload)
|
|
require.NoError(t, err)
|
|
|
|
workflows, ok := listPayload["workflows"].([]interface{})
|
|
require.True(t, ok)
|
|
assert.Len(t, workflows, 2)
|
|
|
|
// Client 2 lists workflow steps (created automatically with workflow)
|
|
stepResp, err := client2.SendRequest("c2-step-list-1", ws.ActionWorkflowStepList, map[string]interface{}{
|
|
"workflow_id": workflowID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var stepPayload map[string]interface{}
|
|
err = stepResp.ParsePayload(&stepPayload)
|
|
require.NoError(t, err)
|
|
steps := stepPayload["steps"].([]interface{})
|
|
require.NotEmpty(t, steps)
|
|
workflowStepID := steps[0].(map[string]interface{})["id"].(string)
|
|
|
|
// Client 1 can also see the workflow steps
|
|
stepListResp, err := client1.SendRequest("c1-step-list-1", ws.ActionWorkflowStepList, map[string]interface{}{
|
|
"workflow_id": workflowID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var stepListPayload map[string]interface{}
|
|
err = stepListResp.ParsePayload(&stepListPayload)
|
|
require.NoError(t, err)
|
|
|
|
stepsList, ok := stepListPayload["steps"].([]interface{})
|
|
require.True(t, ok)
|
|
assert.GreaterOrEqual(t, len(stepsList), 1)
|
|
|
|
// Client 1 creates a task in the workflow step
|
|
taskResp, err := client1.SendRequest("c1-task-1", ws.ActionTaskCreate, map[string]interface{}{
|
|
"workspace_id": workspaceID,
|
|
"workflow_id": workflowID,
|
|
"workflow_step_id": workflowStepID,
|
|
"title": "Task by Client 1",
|
|
"repository_id": repositoryID,
|
|
"base_branch": "main",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var taskPayload map[string]interface{}
|
|
err = taskResp.ParsePayload(&taskPayload)
|
|
require.NoError(t, err)
|
|
|
|
// Client 2 can see the task
|
|
taskListResp, err := client2.SendRequest("c2-task-list-1", ws.ActionTaskList, map[string]interface{}{
|
|
"workflow_id": workflowID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var taskListPayload map[string]interface{}
|
|
err = taskListResp.ParsePayload(&taskListPayload)
|
|
require.NoError(t, err)
|
|
|
|
tasks, ok := taskListPayload["tasks"].([]interface{})
|
|
require.True(t, ok)
|
|
assert.Len(t, tasks, 1)
|
|
|
|
_ = taskPayload // suppress unused variable warning
|
|
}
|
|
|
|
func TestConcurrentRequests(t *testing.T) {
|
|
ts := NewTestServer(t)
|
|
defer ts.Close()
|
|
|
|
client := NewWSClient(t, ts.Server.URL)
|
|
defer client.Close()
|
|
|
|
workspaceID := createWorkspace(t, client)
|
|
repositoryID := createRepository(t, client, workspaceID)
|
|
|
|
// Create workflow (workflow steps are created automatically)
|
|
workflowResp, _ := client.SendRequest("workflow-1", ws.ActionWorkflowCreate, map[string]interface{}{
|
|
"workspace_id": workspaceID,
|
|
"name": "Concurrent Test Workflow",
|
|
"workflow_template_id": "simple",
|
|
})
|
|
var workflowPayload map[string]interface{}
|
|
if err := workflowResp.ParsePayload(&workflowPayload); err != nil {
|
|
t.Fatalf("failed to parse workflow payload: %v", err)
|
|
}
|
|
workflowID := workflowPayload["id"].(string)
|
|
|
|
// Get first workflow step
|
|
stepResp, _ := client.SendRequest("step-list-1", ws.ActionWorkflowStepList, map[string]interface{}{
|
|
"workflow_id": workflowID,
|
|
})
|
|
var stepListPayload map[string]interface{}
|
|
if err := stepResp.ParsePayload(&stepListPayload); err != nil {
|
|
t.Fatalf("failed to parse step list payload: %v", err)
|
|
}
|
|
steps := stepListPayload["steps"].([]interface{})
|
|
workflowStepID := steps[0].(map[string]interface{})["id"].(string)
|
|
|
|
// Create multiple tasks concurrently
|
|
numTasks := 10
|
|
var wg sync.WaitGroup
|
|
results := make(chan error, numTasks)
|
|
|
|
for i := 0; i < numTasks; i++ {
|
|
wg.Add(1)
|
|
go func(idx int) {
|
|
defer wg.Done()
|
|
|
|
// Create separate client for concurrency
|
|
c := NewWSClient(t, ts.Server.URL)
|
|
defer c.Close()
|
|
|
|
_, err := c.SendRequest(
|
|
"task-"+string(rune('0'+idx)),
|
|
ws.ActionTaskCreate,
|
|
map[string]interface{}{
|
|
"workspace_id": workspaceID,
|
|
"workflow_id": workflowID,
|
|
"workflow_step_id": workflowStepID,
|
|
"title": "Concurrent Task " + string(rune('0'+idx)),
|
|
"repository_id": repositoryID,
|
|
"base_branch": "main",
|
|
},
|
|
)
|
|
results <- err
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(results)
|
|
|
|
// Verify all tasks were created
|
|
for err := range results {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// Verify task count
|
|
listResp, err := client.SendRequest("list-1", ws.ActionTaskList, map[string]interface{}{
|
|
"workflow_id": workflowID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var listPayload map[string]interface{}
|
|
err = listResp.ParsePayload(&listPayload)
|
|
require.NoError(t, err)
|
|
|
|
tasks, ok := listPayload["tasks"].([]interface{})
|
|
require.True(t, ok)
|
|
assert.Len(t, tasks, numTasks)
|
|
}
|
|
|
|
func TestTaskSubscription(t *testing.T) {
|
|
ts := NewTestServer(t)
|
|
defer ts.Close()
|
|
|
|
client := NewWSClient(t, ts.Server.URL)
|
|
defer client.Close()
|
|
|
|
workspaceID := createWorkspace(t, client)
|
|
repositoryID := createRepository(t, client, workspaceID)
|
|
|
|
// Create a task first (workflow steps are created automatically with workflow)
|
|
workflowResp, _ := client.SendRequest("workflow-1", ws.ActionWorkflowCreate, map[string]interface{}{
|
|
"workspace_id": workspaceID,
|
|
"name": "Sub Test Workflow",
|
|
"workflow_template_id": "simple",
|
|
})
|
|
var workflowPayload map[string]interface{}
|
|
if err := workflowResp.ParsePayload(&workflowPayload); err != nil {
|
|
t.Fatalf("failed to parse workflow payload: %v", err)
|
|
}
|
|
workflowID := workflowPayload["id"].(string)
|
|
|
|
// Get first workflow step
|
|
stepResp, _ := client.SendRequest("step-list-1", ws.ActionWorkflowStepList, map[string]interface{}{
|
|
"workflow_id": workflowID,
|
|
})
|
|
var stepListPayload map[string]interface{}
|
|
if err := stepResp.ParsePayload(&stepListPayload); err != nil {
|
|
t.Fatalf("failed to parse step list payload: %v", err)
|
|
}
|
|
steps := stepListPayload["steps"].([]interface{})
|
|
workflowStepID := steps[0].(map[string]interface{})["id"].(string)
|
|
|
|
taskResp, _ := client.SendRequest("task-1", ws.ActionTaskCreate, map[string]interface{}{
|
|
"workspace_id": workspaceID,
|
|
"workflow_id": workflowID,
|
|
"workflow_step_id": workflowStepID,
|
|
"title": "Subscribable Task",
|
|
"repository_id": repositoryID,
|
|
"base_branch": "main",
|
|
})
|
|
var taskPayload map[string]interface{}
|
|
if err := taskResp.ParsePayload(&taskPayload); err != nil {
|
|
t.Fatalf("failed to parse task payload: %v", err)
|
|
}
|
|
taskID := taskPayload["id"].(string)
|
|
|
|
// Subscribe to the task
|
|
t.Run("Subscribe", func(t *testing.T) {
|
|
resp, err := client.SendRequest("sub-1", ws.ActionTaskSubscribe, map[string]interface{}{
|
|
"task_id": taskID,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ws.MessageTypeResponse, resp.Type)
|
|
|
|
var payload map[string]interface{}
|
|
err = resp.ParsePayload(&payload)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, true, payload["success"])
|
|
assert.Equal(t, taskID, payload["task_id"])
|
|
})
|
|
|
|
// Unsubscribe from the task
|
|
t.Run("Unsubscribe", func(t *testing.T) {
|
|
resp, err := client.SendRequest("unsub-1", ws.ActionTaskUnsubscribe, map[string]interface{}{
|
|
"task_id": taskID,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ws.MessageTypeResponse, resp.Type)
|
|
|
|
var payload map[string]interface{}
|
|
err = resp.ParsePayload(&payload)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, true, payload["success"])
|
|
})
|
|
}
|