18516c8f8b
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
134 lines
4.5 KiB
Go
134 lines
4.5 KiB
Go
package runtime
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/jmoiron/sqlx"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
settingsstore "github.com/kandev/kandev/internal/agent/settings/store"
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
"github.com/kandev/kandev/internal/office/agents"
|
|
"github.com/kandev/kandev/internal/office/models"
|
|
"github.com/kandev/kandev/internal/office/repository/sqlite"
|
|
)
|
|
|
|
// tasklessAnnotationHandlerHarness wires the runtime handler around a
|
|
// taskless run (empty task_id claim) with comment/task dependencies, so
|
|
// POST /runtime/comments can be exercised over HTTP rather than by calling
|
|
// Actions.PostComment directly — the wire path has its own request parsing
|
|
// and audit-event logging that the Actions-level tests never reach.
|
|
type tasklessAnnotationHandlerHarness struct {
|
|
router *gin.Engine
|
|
token string
|
|
comments *handlerCommentWriter
|
|
tasks *handlerTaskCreator
|
|
runEvents *recordingRunEvents
|
|
}
|
|
|
|
func newTasklessAnnotationHandlerHarness(t *testing.T, claimWorkspaceID string) *tasklessAnnotationHandlerHarness {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
db, err := sqlx.Open("sqlite3", ":memory:")
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
if _, _, err := settingsstore.Provide(db, db, nil); err != nil {
|
|
t.Fatalf("settings store init: %v", err)
|
|
}
|
|
repo, err := sqlite.NewWithDB(db, db, nil)
|
|
if err != nil {
|
|
t.Fatalf("new repo: %v", err)
|
|
}
|
|
agentSvc := agents.NewAgentService(repo, logger.Default(), nil)
|
|
agentSvc.SetAuth(agents.NewAgentAuth("taskless-annotation-test-key"))
|
|
agent := &models.AgentInstance{
|
|
ID: "agent-1",
|
|
WorkspaceID: "ws-agent-home",
|
|
Name: "Coordinator",
|
|
Role: models.AgentRoleCEO,
|
|
}
|
|
if err := repo.CreateAgentInstance(context.Background(), agent); err != nil {
|
|
t.Fatalf("create agent: %v", err)
|
|
}
|
|
capabilityJSON, err := MarshalCapabilities(Capabilities{CanPostComments: true})
|
|
if err != nil {
|
|
t.Fatalf("marshal capabilities: %v", err)
|
|
}
|
|
token, err := agentSvc.MintRuntimeJWT("agent-1", "", claimWorkspaceID, "run-1", "sess-1", capabilityJSON)
|
|
if err != nil {
|
|
t.Fatalf("mint runtime token: %v", err)
|
|
}
|
|
comments := &handlerCommentWriter{}
|
|
tasks := &handlerTaskCreator{}
|
|
runEvents := &recordingRunEvents{}
|
|
router := gin.New()
|
|
RegisterRoutes(router.Group(""), NewHandler(
|
|
agentSvc,
|
|
NewActions(ActionDependencies{Comments: comments, Tasks: tasks, Agents: agentSvc}),
|
|
nil,
|
|
runEvents,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
))
|
|
return &tasklessAnnotationHandlerHarness{
|
|
router: router, token: token, comments: comments, tasks: tasks, runEvents: runEvents,
|
|
}
|
|
}
|
|
|
|
func (h *tasklessAnnotationHandlerHarness) postComment(t *testing.T, taskID, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
payload, err := json.Marshal(map[string]string{"task_id": taskID, "body": body})
|
|
if err != nil {
|
|
t.Fatalf("marshal payload: %v", err)
|
|
}
|
|
req := httptest.NewRequest(http.MethodPost, "/runtime/comments", bytes.NewReader(payload))
|
|
req.Header.Set("Authorization", "Bearer "+h.token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp := httptest.NewRecorder()
|
|
h.router.ServeHTTP(resp, req)
|
|
return resp
|
|
}
|
|
|
|
func TestRuntimeHandler_TasklessRunAnnotatesWorkspaceTaskOverHTTP(t *testing.T) {
|
|
h := newTasklessAnnotationHandlerHarness(t, "ws-1")
|
|
|
|
resp := h.postComment(t, "task-x", "blocker found")
|
|
|
|
if resp.Code != http.StatusCreated {
|
|
t.Fatalf("status = %d, want %d; body=%s", resp.Code, http.StatusCreated, resp.Body.String())
|
|
}
|
|
if len(h.comments.comments) != 1 {
|
|
t.Fatalf("comments = %d, want 1", len(h.comments.comments))
|
|
}
|
|
comment := h.comments.comments[0]
|
|
if comment.TaskID != "task-x" || comment.AuthorID != "agent-1" || comment.AuthorType != "agent" {
|
|
t.Fatalf("comment identity = %#v", comment)
|
|
}
|
|
assertActionRunEvent(t, h.runEvents, "post_comment", "task", "task-x")
|
|
}
|
|
|
|
func TestRuntimeHandler_PostCommentAuditEventNamesTrimmedTarget(t *testing.T) {
|
|
h := newTasklessAnnotationHandlerHarness(t, "ws-1")
|
|
|
|
resp := h.postComment(t, " task-x ", "blocker found")
|
|
|
|
if resp.Code != http.StatusCreated {
|
|
t.Fatalf("status = %d, want %d; body=%s", resp.Code, http.StatusCreated, resp.Body.String())
|
|
}
|
|
if len(h.comments.comments) != 1 || h.comments.comments[0].TaskID != "task-x" {
|
|
t.Fatalf("comments = %#v, want one comment on trimmed task-x", h.comments.comments)
|
|
}
|
|
// The audit event must name the task the comment actually landed on,
|
|
// not the padded value the caller supplied.
|
|
assertActionRunEvent(t, h.runEvents, "post_comment", "task", "task-x")
|
|
}
|