Files
Agent-System-Backend/internal/server/workflow_route_test.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

154 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package server
import (
"context"
"encoding/json"
"testing"
"time"
agentcommand "git-ssh.u1680832.nyat.app/weichen/Agent-Command.git"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/cmdbus"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/intentgate"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/runtime"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/settings"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/store"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/workflow"
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
type routeCapture struct {
ch chan workflow.Launch
}
func (c routeCapture) Name() string { return "route-capture" }
func (c routeCapture) Execute(rc runtime.RunCtx) error {
src, _ := rc.(interface {
Workflow() (workflow.Launch, bool)
})
if src == nil {
return nil
}
if launch, ok := src.Workflow(); ok {
c.ch <- launch
}
return nil
}
func TestCommandBusBeatsWorkflow(t *testing.T) {
srv, st := newTestServer(t)
if err := st.MergeSettingsScope(store.OwnerDefault, settings.ScopeUser, map[string]any{"runtime": map[string]any{"mock": false}}); err != nil {
t.Fatal(err)
}
ch := make(chan workflow.Launch, 1)
srv.mgr.SetWorkflowRunner(routeCapture{ch})
gate := intentgate.New()
if err := gate.Rebuild(context.Background(), []json.RawMessage{briefIntent()}); err != nil {
t.Fatal(err)
}
srv.Gate = gate
reg := workflow.NewRegistry()
reg.Set([]core.WorkflowDef{{
ID: "demo.brief", Title: "简报", IntentID: "demo.brief",
Steps: []core.WorkflowStep{{ID: "s", Kind: "hitl"}},
}}, nil)
srv.Workflows = reg
if err := srv.Bus.RegisterBuiltin(agentcommand.CommandSpec{ID: "demo.brief", Title: "简报命令"}, "", func(context.Context, cmdbus.Request) (any, error) {
return map[string]any{"ok": true}, nil
}); err != nil {
t.Fatal(err)
}
_, convID := setupConversation(t, srv.Handler())
code, out := doJSON(t, srv.Handler(), "POST", "/api/conversations/"+convID+"/messages", map[string]any{"text": "写一份简报"})
if code != 200 || out["command"] != "demo.brief" {
t.Fatalf("status %d body %v", code, out)
}
if _, ok := out["run"]; ok {
t.Fatal("a command hit must not start a workflow run")
}
select {
case <-ch:
t.Fatal("workflow runner started for a command")
default:
}
}
func TestIntentStartsWorkflowRun(t *testing.T) {
srv, st := newTestServer(t)
if err := st.MergeSettingsScope(store.OwnerDefault, settings.ScopeUser, map[string]any{"runtime": map[string]any{"mock": false}}); err != nil {
t.Fatal(err)
}
ch := make(chan workflow.Launch, 1)
srv.mgr.SetWorkflowRunner(routeCapture{ch})
gate := intentgate.New()
if err := gate.Rebuild(context.Background(), []json.RawMessage{briefIntent()}); err != nil {
t.Fatal(err)
}
srv.Gate = gate
reg := workflow.NewRegistry()
reg.Set([]core.WorkflowDef{{
ID: "demo.brief", Title: "简报", IntentID: "demo.brief",
Steps: []core.WorkflowStep{{ID: "s", Kind: "hitl"}},
}}, nil)
srv.Workflows = reg
_, convID := setupConversation(t, srv.Handler())
code, out := doJSON(t, srv.Handler(), "POST", "/api/conversations/"+convID+"/messages", map[string]any{"text": "写一份简报"})
if code != 200 || out["run"] == nil {
t.Fatalf("status %d body %v", code, out)
}
select {
case launch := <-ch:
if launch.Def.ID != "demo.brief" || len(launch.Missing) != 1 || launch.Missing[0].Question != "主题是什么?" {
t.Fatalf("%+v", launch)
}
case <-time.After(time.Second):
t.Fatal("workflow runner was not selected")
}
}
func TestWorkflowRouteClaimsBeforePersist(t *testing.T) {
srv, st := newTestServer(t)
if err := st.MergeSettingsScope(store.OwnerDefault, settings.ScopeUser, map[string]any{"runtime": map[string]any{"mock": false}}); err != nil {
t.Fatal(err)
}
gate := intentgate.New()
if err := gate.Rebuild(context.Background(), []json.RawMessage{briefIntent()}); err != nil {
t.Fatal(err)
}
srv.Gate = gate
reg := workflow.NewRegistry()
reg.Set([]core.WorkflowDef{{
ID: "demo.brief", Title: "简报", IntentID: "demo.brief",
Steps: []core.WorkflowStep{{ID: "s", Kind: "hitl"}},
}}, nil)
srv.Workflows = reg
_, convID := setupConversation(t, srv.Handler())
conv, err := st.GetConversation(store.OwnerDefault, convID)
if err != nil {
t.Fatal(err)
}
if err := srv.mgr.ClaimConversation(convID); err != nil {
t.Fatal(err)
}
before, err := st.ListMessages(convID)
if err != nil {
t.Fatal(err)
}
code, out := doJSON(t, srv.Handler(), "POST", "/api/conversations/"+convID+"/messages", map[string]any{"text": "写一份简报"})
if code != 409 {
t.Fatalf("status %d body %v", code, out)
}
after, err := st.ListMessages(convID)
if err != nil {
t.Fatal(err)
}
if len(after) != len(before) {
t.Fatalf("workflow route wrote a message while busy")
}
srv.mgr.ReleaseConversation(conv.ID)
}
func briefIntent() json.RawMessage {
return json.RawMessage(`{"id":"demo.brief","domain":"demo","version":1,"matcher":{"includes_any":["简报"]},"slots":[{"key":"topic","required":true,"pattern":"主题[::]\\s*(\\S+)","question":"主题是什么?"}]}`)
}