Why: Background notice runs blocked chat, truncated results, and duplicated deliverable execution. Evidence: Focused run manager, card, server, recovery and race regressions pass locally; final combination checks are tracked in Dev. Impact: Persist full results and optional choices, fence dispatch by run identity, retain public child process, and repair legacy projections without a schema change. No paid model trial was run. Refs: feature/workbench-flow-completion
64 lines
2.8 KiB
Go
64 lines
2.8 KiB
Go
package server
|
|
|
|
import (
|
|
"agent-system-backend/internal/store"
|
|
core "agent-system-core"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestWorkActionsAreNonblockingAndDoNotAppendReceipt(t *testing.T) {
|
|
srv, st := newTestServer(t)
|
|
h := srv.Handler()
|
|
projectID, convID := setupConversation(t, h)
|
|
card, err := srv.Cards.CreateAccountChecked(store.OwnerDefault, core.ActivityCard{ProjectID: projectID, ConversationID: convID, Kind: core.CardKindProgress, Status: core.CardDone, Title: "材料整理", Work: &core.WorkCard{Result: "完整的材料结论和来源。", ResultVersion: 1, Decision: &core.WorkDecision{Mode: "single", State: "open"}}}, "", true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, action := range []string{"work.defer", "work.read", "work.reopen"} {
|
|
code, body := doJSON(t, h, http.MethodPost, "/api/cards/"+card.ID+"/actions/"+action, map[string]any{"resultVersion": 1})
|
|
if code != http.StatusOK {
|
|
t.Fatalf("%s: %d %v", action, code, body)
|
|
}
|
|
if _, ok := body["receipt"]; ok {
|
|
t.Fatalf("%s appended receipt: %v", action, body)
|
|
}
|
|
}
|
|
messages, _ := st.ListMessages(convID)
|
|
if len(messages) != 0 {
|
|
t.Fatalf("optional choice wrote chat messages: %v", messages)
|
|
}
|
|
run := postMessage(t, h, convID, "现在聊聊别的事情")
|
|
waitRunTerminal(t, h, run, 5*time.Second)
|
|
code, _ := doJSON(t, h, http.MethodPost, "/api/cards/"+card.ID+"/actions/work.read", map[string]any{"resultVersion": 2})
|
|
if code != http.StatusConflict {
|
|
t.Fatalf("version fence status %d", code)
|
|
}
|
|
}
|
|
|
|
func TestExplicitWorkReferenceRejectsOtherConversationBeforeWriting(t *testing.T) {
|
|
srv, st := newTestServer(t)
|
|
h := srv.Handler()
|
|
projectID, convID := setupConversation(t, h)
|
|
_, otherID := setupConversation(t, h)
|
|
card, err := srv.Cards.CreateAccountChecked(store.OwnerDefault, core.ActivityCard{ProjectID: projectID, ConversationID: convID, Kind: core.CardKindProgress, Status: core.CardDone, Work: &core.WorkCard{Result: "private task result", ResultVersion: 1}}, "", true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
code, _ := doJSON(t, h, http.MethodPost, "/api/conversations/"+otherID+"/messages", map[string]any{"text": "请解释其中的来源", "requestId": "work-other-conv", "taskRef": map[string]any{"cardId": card.ID, "resultVersion": 1}})
|
|
if code != http.StatusNotFound {
|
|
t.Fatalf("cross-conversation reference status %d", code)
|
|
}
|
|
messages, _ := st.ListMessages(otherID)
|
|
if len(messages) != 0 {
|
|
t.Fatalf("rejected reference persisted message: %v", messages)
|
|
}
|
|
code, body := doJSON(t, h, http.MethodPost, "/api/conversations/"+convID+"/messages", map[string]any{"text": "请解释其中的来源", "requestId": "work-same-conv", "taskRef": map[string]any{"cardId": card.ID, "resultVersion": 1}})
|
|
if code != http.StatusOK {
|
|
t.Fatalf("valid reference %d %v", code, body)
|
|
}
|
|
run := body["run"].(map[string]any)["id"].(string)
|
|
waitRunTerminal(t, h, run, 5*time.Second)
|
|
}
|