package orbit import ( "context" "encoding/json" "strings" "testing" "time" ) func TestIntegrationWorkflowEventsSDK(t *testing.T) { api, queue, _ := integrationWorkerClient(t) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() operation := workerTestID(t) spec := workflowSDKSpec(queue, "events."+operation) spec.Body = WorkflowBlock{Steps: []WorkflowStep{ {Key: "receipt", Kind: "SIGNAL", Signal: &WorkflowSignal{Name: "done", PayloadVersion: "v1", Correlation: WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(`"` + operation + `"`)}, PayloadType: spec.InputType, TimeoutMS: 60000}}, {Key: "delay", Kind: "TIMER", Timer: &WorkflowTimer{DelayMS: 60000}}, }, OutputType: WorkflowType{Kind: "NULL"}, Output: WorkflowRef("delay")} v := startWorkflowSDK(t, ctx, api, spec) sent, err := api.SendWorkflowSignal(ctx, v.ID, operation, SendWorkflowSignalRequest{Name: "done", PayloadVersion: "v1", TargetStepKey: "receipt", TargetActivationSequence: 1, CorrelationKey: operation, Payload: workflowExact, Reason: "verify timeline"}) if err != nil { t.Fatal(err) } waitWorkflowStepSDK(t, ctx, api, v.ID, "receipt", func(d WorkflowStepDetail) bool { return d.Step.State == "SUCCEEDED" }) current, err := api.GetWorkflow(ctx, v.ID) if err != nil { t.Fatal(err) } if _, err = api.ControlWorkflow(ctx, v.ID, operation, WorkflowControlRequest{Operation: "CANCEL", ExpectedControlRevision: current.ControlRevision, Reason: "timeline cleanup"}); err != nil { t.Fatal(err) } waitWorkflowSDK(t, ctx, api, v.ID, "CANCELLED") seen := map[string]bool{} types := map[string]bool{} cursor := "" pages := 0 for { page, err := api.ListWorkflowEvents(ctx, v.ID, HistoryPageRequest{Limit: 2, Cursor: cursor}) if err != nil { t.Fatal(err) } pages++ raw, _ := json.Marshal(page) if strings.Contains(string(raw), `"payload"`) || strings.Contains(string(raw), `9007199254740993`) { t.Fatal("payload in summaries") } for _, e := range page.Items { if seen[e.ID] || e.WorkflowID != v.ID || e.StepKey == "$root" { t.Fatal("event identity", e) } seen[e.ID] = true types[e.Type] = true detail, err := api.GetWorkflowEvent(ctx, v.ID, e.ID) if err != nil || detail.WorkflowEventSummary != e { t.Fatal("detail", detail, err) } if e.AggregateKind == "workflow_signal" && (e.AggregateID != sent.Signal.ID || e.State != "CONSUMED" || e.StateScope != "SIGNAL") { t.Fatal("signal transition", e) } } if page.NextCursor == "" { break } cursor = page.NextCursor if pages > 20 { t.Fatal("unbounded page repetition") } } for _, kind := range []string{"workflow.started", "workflow.step.changed", "workflow.signal.accepted", "workflow.signal.changed", "workflow.controlled", "workflow.stopped"} { if !types[kind] { t.Fatal("event missing", kind, types) } } if len(seen) < 7 || pages < 4 { t.Fatal("incomplete timeline", len(seen), pages) } t.Logf("real HTTP event timeline: %d immutable events over %d pages; signal transition and cancelled workflow retained", len(seen), pages) }