3e237e177a
* feat: add code-walkthrough backend (MCP tool, service, persistence, WS events) * feat: render agent code walkthroughs as line-anchored cards + editor window * test: add code-walkthrough re-emit mock-agent scenario * fix: rework code-walkthrough UX (floating card, line reveal, minimize, clickable file) * fix: improve code walkthrough editor UX * fix: backfill walkthrough launcher after websocket connect * fix: improve walkthrough requests and editor anchors * fix: clear walkthrough highlights on close * feat: add walkthrough discard and prompt customization * fix: send walkthrough requests through saved prompt * fix: support postgres built-in prompt seeding * fix: address walkthrough review feedback * fix: address walkthrough review follow-ups * fix: harden walkthrough lifecycle races * fix: reuse walkthrough notification cleanup * fix: guard stale walkthrough file opens * fix: abort stale file opens before paint * fix: stabilize github settings e2e readiness * fix: use stable github settings e2e selector
137 lines
4.3 KiB
Go
137 lines
4.3 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/task/models"
|
|
)
|
|
|
|
func seedWalkthroughTask(t *testing.T, ctx context.Context, repo *Repository, taskID string) {
|
|
t.Helper()
|
|
const wsID = "ws-wt"
|
|
const wfID = "wf-wt"
|
|
_ = repo.CreateWorkspace(ctx, &models.Workspace{ID: wsID, Name: "WT WS"})
|
|
_ = repo.CreateWorkflow(ctx, &models.Workflow{ID: wfID, WorkspaceID: wsID, Name: "WF"})
|
|
if err := repo.CreateTask(ctx, &models.Task{
|
|
ID: taskID,
|
|
WorkspaceID: wsID,
|
|
WorkflowID: wfID,
|
|
Title: "t",
|
|
State: "BACKLOG",
|
|
Priority: "medium",
|
|
}); err != nil {
|
|
t.Fatalf("CreateTask: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWalkthroughRepo_CreateGet(t *testing.T) {
|
|
repo := newRepoForSessionTests(t)
|
|
ctx := context.Background()
|
|
seedWalkthroughTask(t, ctx, repo, "task-1")
|
|
|
|
wt := &models.TaskWalkthrough{
|
|
TaskID: "task-1",
|
|
Title: "Tour",
|
|
Steps: []models.WalkthroughStep{
|
|
{Title: "Start", Repo: "main", File: "a.go", Line: 3, Text: "first"},
|
|
{File: "b.go", Line: 9, LineEnd: 12, Text: "second"},
|
|
},
|
|
}
|
|
if err := repo.CreateTaskWalkthrough(ctx, wt); err != nil {
|
|
t.Fatalf("CreateTaskWalkthrough: %v", err)
|
|
}
|
|
if wt.ID == "" || wt.CreatedAt.IsZero() {
|
|
t.Fatalf("expected id+timestamps populated: %+v", wt)
|
|
}
|
|
|
|
got, err := repo.GetTaskWalkthrough(ctx, "task-1")
|
|
if err != nil {
|
|
t.Fatalf("GetTaskWalkthrough: %v", err)
|
|
}
|
|
if got == nil || got.Title != "Tour" || len(got.Steps) != 2 {
|
|
t.Fatalf("unexpected walkthrough: %+v", got)
|
|
}
|
|
if got.Steps[1].File != "b.go" || got.Steps[1].LineEnd != 12 {
|
|
t.Fatalf("steps round-trip mismatch: %+v", got.Steps)
|
|
}
|
|
}
|
|
|
|
func TestWalkthroughRepo_Upsert(t *testing.T) {
|
|
repo := newRepoForSessionTests(t)
|
|
ctx := context.Background()
|
|
seedWalkthroughTask(t, ctx, repo, "task-1")
|
|
|
|
first := &models.TaskWalkthrough{TaskID: "task-1", Steps: []models.WalkthroughStep{{File: "a.go", Line: 1, Text: "x"}}}
|
|
if err := repo.CreateTaskWalkthrough(ctx, first); err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
second := &models.TaskWalkthrough{ID: first.ID, TaskID: "task-1", Steps: []models.WalkthroughStep{{File: "b.go", Line: 2, Text: "y"}, {File: "c.go", Line: 3, Text: "z"}}}
|
|
if err := repo.CreateTaskWalkthrough(ctx, second); err != nil {
|
|
t.Fatalf("upsert: %v", err)
|
|
}
|
|
if second.ID != first.ID || !second.CreatedAt.Equal(first.CreatedAt) {
|
|
t.Fatalf("expected upsert to reuse persisted id+created_at: first=%+v second=%+v", first, second)
|
|
}
|
|
|
|
got, _ := repo.GetTaskWalkthrough(ctx, "task-1")
|
|
if got == nil || len(got.Steps) != 2 || got.Steps[0].File != "b.go" {
|
|
t.Fatalf("upsert did not replace steps: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestWalkthroughRepo_GetMissing(t *testing.T) {
|
|
repo := newRepoForSessionTests(t)
|
|
ctx := context.Background()
|
|
seedWalkthroughTask(t, ctx, repo, "task-1")
|
|
|
|
got, err := repo.GetTaskWalkthrough(ctx, "task-1")
|
|
if err != nil {
|
|
t.Fatalf("GetTaskWalkthrough: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Fatalf("expected nil, got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestWalkthroughRepo_Delete(t *testing.T) {
|
|
repo := newRepoForSessionTests(t)
|
|
ctx := context.Background()
|
|
seedWalkthroughTask(t, ctx, repo, "task-1")
|
|
|
|
if err := repo.CreateTaskWalkthrough(ctx, &models.TaskWalkthrough{TaskID: "task-1", Steps: []models.WalkthroughStep{{File: "a.go", Line: 1, Text: "x"}}}); err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if err := repo.DeleteTaskWalkthrough(ctx, "task-1"); err != nil {
|
|
t.Fatalf("delete: %v", err)
|
|
}
|
|
got, _ := repo.GetTaskWalkthrough(ctx, "task-1")
|
|
if got != nil {
|
|
t.Fatalf("expected nil after delete, got %+v", got)
|
|
}
|
|
if err := repo.DeleteTaskWalkthrough(ctx, "task-1"); !errors.Is(err, models.ErrTaskWalkthroughNotFound) {
|
|
t.Fatalf("expected ErrTaskWalkthroughNotFound deleting missing walkthrough, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWalkthroughRepo_CascadeOnTaskDelete(t *testing.T) {
|
|
repo := newRepoForSessionTests(t)
|
|
ctx := context.Background()
|
|
seedWalkthroughTask(t, ctx, repo, "task-1")
|
|
|
|
if err := repo.CreateTaskWalkthrough(ctx, &models.TaskWalkthrough{TaskID: "task-1", Steps: []models.WalkthroughStep{{File: "a.go", Line: 1, Text: "x"}}}); err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if err := repo.DeleteTask(ctx, "task-1"); err != nil {
|
|
t.Fatalf("DeleteTask: %v", err)
|
|
}
|
|
got, err := repo.GetTaskWalkthrough(ctx, "task-1")
|
|
if err != nil {
|
|
t.Fatalf("GetTaskWalkthrough after task delete: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Fatalf("expected walkthrough cascade-deleted, got %+v", got)
|
|
}
|
|
}
|