Files
2026-05-16 20:07:57 +01:00

325 lines
9.7 KiB
Go

// Package integration provides end-to-end integration tests for the Kandev backend.
package integration
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ws "github.com/kandev/kandev/pkg/websocket"
)
func TestTaskCRUD(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, err := client.SendRequest("workflow-1", ws.ActionWorkflowCreate, map[string]interface{}{
"workspace_id": workspaceID,
"name": "Task Test 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)
// Get first workflow step
stepResp, err := client.SendRequest("step-list-1", ws.ActionWorkflowStepList, map[string]interface{}{
"workflow_id": workflowID,
})
require.NoError(t, err)
var stepListPayload map[string]interface{}
err = stepResp.ParsePayload(&stepListPayload)
require.NoError(t, err)
steps := stepListPayload["steps"].([]interface{})
require.NotEmpty(t, steps)
workflowStepID := steps[0].(map[string]interface{})["id"].(string)
var taskID string
// Create task
t.Run("CreateTask", func(t *testing.T) {
resp, err := client.SendRequest("task-create-1", ws.ActionTaskCreate, map[string]interface{}{
"workspace_id": workspaceID,
"workflow_id": workflowID,
"workflow_step_id": workflowStepID,
"title": "Test Task",
"description": "A test task for integration testing",
"priority": "high",
"repository_id": repositoryID,
"base_branch": "main",
})
require.NoError(t, err)
// Debug: print error if there is one
if resp.Type == ws.MessageTypeError {
var errPayload ws.ErrorPayload
if err := resp.ParsePayload(&errPayload); err != nil {
t.Fatalf("failed to parse error payload: %v", err)
}
t.Logf("Error response: %+v", errPayload)
}
require.Equal(t, ws.MessageTypeResponse, resp.Type, "Expected response but got %s", resp.Type)
var payload map[string]interface{}
err = resp.ParsePayload(&payload)
require.NoError(t, err)
taskID = payload["id"].(string)
assert.NotEmpty(t, taskID)
assert.Equal(t, "Test Task", payload["title"])
assert.Equal(t, "CREATED", payload["state"])
})
// Get task
t.Run("GetTask", func(t *testing.T) {
resp, err := client.SendRequest("task-get-1", ws.ActionTaskGet, map[string]interface{}{
"id": taskID,
})
require.NoError(t, err)
var payload map[string]interface{}
err = resp.ParsePayload(&payload)
require.NoError(t, err)
assert.Equal(t, taskID, payload["id"])
assert.Equal(t, "Test Task", payload["title"])
})
// Update task
t.Run("UpdateTask", func(t *testing.T) {
resp, err := client.SendRequest("task-update-1", ws.ActionTaskUpdate, map[string]interface{}{
"id": taskID,
"title": "Updated Task Title",
})
require.NoError(t, err)
var payload map[string]interface{}
err = resp.ParsePayload(&payload)
require.NoError(t, err)
assert.Equal(t, "Updated Task Title", payload["title"])
})
// List tasks
t.Run("ListTasks", func(t *testing.T) {
resp, err := client.SendRequest("task-list-1", ws.ActionTaskList, map[string]interface{}{
"workflow_id": workflowID,
})
require.NoError(t, err)
var payload map[string]interface{}
err = resp.ParsePayload(&payload)
require.NoError(t, err)
tasks, ok := payload["tasks"].([]interface{})
require.True(t, ok)
assert.Len(t, tasks, 1)
})
// Delete task
t.Run("DeleteTask", func(t *testing.T) {
resp, err := client.SendRequest("task-delete-1", ws.ActionTaskDelete, map[string]interface{}{
"id": taskID,
})
require.NoError(t, err)
var payload map[string]interface{}
err = resp.ParsePayload(&payload)
require.NoError(t, err)
assert.Equal(t, true, payload["success"])
// Verify task is deleted
listResp, err := client.SendRequest("task-list-2", 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, 0)
})
}
func TestTaskStateTransitions(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 and task (workflow steps are created automatically)
workflowResp, _ := client.SendRequest("workflow-1", ws.ActionWorkflowCreate, map[string]interface{}{
"workspace_id": workspaceID,
"name": "State 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": "State Test Task",
"description": "Test state transitions",
"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)
// Test state transitions
stateTests := []struct {
name string
newState string
}{
{"ToInProgress", "IN_PROGRESS"},
{"ToCompleted", "COMPLETED"},
{"BackToTodo", "TODO"},
{"ToBlocked", "BLOCKED"},
}
for i, tc := range stateTests {
t.Run(tc.name, func(t *testing.T) {
resp, err := client.SendRequest(
"state-"+string(rune('0'+i)),
ws.ActionTaskState,
map[string]interface{}{
"id": taskID,
"state": tc.newState,
},
)
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, tc.newState, payload["state"])
})
}
}
func TestTaskMove(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": "Move 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 workflow steps (at least 2 should exist from default template)
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{})
require.GreaterOrEqual(t, len(steps), 2, "Expected at least 2 workflow steps")
step1ID := steps[0].(map[string]interface{})["id"].(string)
step2ID := steps[1].(map[string]interface{})["id"].(string)
// Create task in first step
taskResp, _ := client.SendRequest("task-1", ws.ActionTaskCreate, map[string]interface{}{
"workspace_id": workspaceID,
"workflow_id": workflowID,
"workflow_step_id": step1ID,
"title": "Movable 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)
// Move task to second step
t.Run("MoveToStep2", func(t *testing.T) {
resp, err := client.SendRequest("move-1", ws.ActionTaskMove, map[string]interface{}{
"id": taskID,
"workflow_id": workflowID,
"workflow_step_id": step2ID,
"position": 0,
})
require.NoError(t, err)
var payload map[string]interface{}
err = resp.ParsePayload(&payload)
require.NoError(t, err)
// Response is {"task": {...}, "workflow_step": {...}}
taskData := payload["task"].(map[string]interface{})
assert.Equal(t, step2ID, taskData["workflow_step_id"])
})
// Verify task is now in step 2
t.Run("VerifyMove", func(t *testing.T) {
resp, err := client.SendRequest("get-1", ws.ActionTaskGet, map[string]interface{}{
"id": taskID,
})
require.NoError(t, err)
var payload map[string]interface{}
err = resp.ParsePayload(&payload)
require.NoError(t, err)
assert.Equal(t, step2ID, payload["workflow_step_id"])
})
}