feat: add scoped workflow instance queries and SDK pagination

This commit is contained in:
2026-09-18 08:34:45 +08:00
parent 17a8e0126c
commit 082898e705
33 changed files with 1598 additions and 18 deletions

View File

@@ -0,0 +1,84 @@
package orbit
import (
"context"
"encoding/json"
"net/http"
"strings"
"testing"
"time"
)
func TestIntegrationWorkflowQuerySDK(t *testing.T) {
api, queue, token := integrationWorkerClient(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
spec := workflowSDKSpec(queue, "query."+workerTestID(t))
// SIGNAL保持稳定WAITING,不启动Worker或等待短时任务截止。
spec.Body = WorkflowBlock{Steps: []WorkflowStep{{Key: "receipt", Kind: "SIGNAL", Signal: &WorkflowSignal{Name: "done", PayloadVersion: "v1", Correlation: WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(`"query"`)}, PayloadType: spec.InputType, TimeoutMS: 60000}}}, OutputType: spec.InputType, Output: WorkflowRef("receipt")}
in := publishWorkflowSDK(t, ctx, api, spec)
in.BusinessKey = "作业 /+%\n " + workerTestID(t) + " "
var started []WorkflowInstance
for _, key := range []string{in.BusinessKey, in.BusinessKey, strings.TrimSpace(in.BusinessKey)} {
in.BusinessKey = key
out, err := api.StartWorkflow(ctx, workerTestID(t), in)
if err != nil {
t.Fatal(err)
}
started = append(started, out.Instance)
}
paused, err := api.ControlWorkflow(ctx, started[0].ID, workerTestID(t), WorkflowControlRequest{Operation: "PAUSE", ExpectedControlRevision: started[0].ControlRevision, Reason: "verify paused query"})
if err != nil || !paused.Instance.Paused {
t.Fatal(paused, err)
}
from, before := started[0].CreatedAt, started[2].CreatedAt.Add(time.Microsecond)
query := ListWorkflowsRequest{DefinitionID: in.DefinitionID, Version: "v1", State: "WAITING", BusinessKey: started[0].BusinessKey, CreatedFrom: &from, CreatedBefore: &before, Limit: 1}
first, err := api.ListWorkflows(ctx, query)
if err != nil || len(first.Items) != 1 || first.Items[0].ID != started[1].ID || first.NextCursor == "" {
t.Fatal(first, err)
}
query.Cursor = first.NextCursor
last, err := api.ListWorkflows(ctx, query)
if err != nil || len(last.Items) != 1 || last.Items[0].ID != started[0].ID || !last.Items[0].Paused || last.NextCursor != "" {
t.Fatal(last, err)
}
query.BusinessKey = strings.TrimSpace(query.BusinessKey)
_, err = api.ListWorkflows(ctx, query)
workflowAPIError(t, err, "INVALID_ARGUMENT")
query.Cursor = ""
if page, err := api.ListWorkflows(ctx, query); err != nil || len(page.Items) != 1 || page.Items[0].ID != started[2].ID {
t.Fatal("exact business key", page, err)
}
b := false
query.BusinessKey = started[0].BusinessKey
query.Paused = &b
if page, err := api.ListWorkflows(ctx, query); err != nil || len(page.Items) != 1 || page.Items[0].ID != started[1].ID {
t.Fatal("false paused filter", page, err)
}
for _, suffix := range []string{"?state=WAITING&state=RUNNING", "?paused=1", "?business_key=", "?unknown=x", "?version=v1", "?limit=101"} {
req, err := http.NewRequestWithContext(ctx, "GET", api.baseURL+"/v1/workflows"+suffix, nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Authorization", "Bearer "+token)
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if res.StatusCode != 400 {
t.Fatal("invalid raw query", suffix, res.StatusCode)
}
}
for _, v := range started {
current, err := api.GetWorkflow(ctx, v.ID)
if err != nil {
t.Fatal(err)
}
if _, err = api.ControlWorkflow(ctx, v.ID, workerTestID(t), WorkflowControlRequest{Operation: "CANCEL", ExpectedControlRevision: current.ControlRevision, Reason: "query regression cleanup"}); err != nil {
t.Fatal(err)
}
waitWorkflowSDK(t, ctx, api, v.ID, "CANCELLED")
}
t.Log("real HTTP: exact key, time bounds, fixed version, paused false, filtered cursor, malformed query rejection and cancellation cleanup")
}