mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
3f77eded9d
Framework support for resource-scoped event subscriptions, so one EventKey can fan out into independent per-resource subscription scopes: - KeyDefinition gains SubscriptionKey / NormalizeParams / Match hooks - ComputeSubscriptionID derives a dedup identity from (EventKey, sub-key params); plumbed through bus Hub, consume loop, and the Hello / PreShutdownCheck / ConsumerInfo protocol messages - add a synchronous Match filter stage before Process - change PreConsume cleanup to func() error and surface cleanup (unsubscribe) failures as WARN with an idempotency note - adapt minutes/vc/whiteboard PreConsume to the new cleanup signature - render SubscriptionID / SubscriptionKey in event status & schema output No domain wires these hooks yet; covered by unit tests using bus/protocol doubles. (Mail, the original exerciser, is intentionally not included.) Change-Id: Ifc743f1aa0bc4dff0c8a1e35da24883694fe7699
127 lines
4.1 KiB
Go
127 lines
4.1 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package consume
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/internal/event"
|
|
)
|
|
|
|
func TestComputeSubscriptionID(t *testing.T) {
|
|
makeDef := func(subKeyNames ...string) *event.KeyDefinition {
|
|
def := &event.KeyDefinition{Key: "test.evt"}
|
|
marked := make(map[string]bool, len(subKeyNames))
|
|
for _, n := range subKeyNames {
|
|
marked[n] = true
|
|
}
|
|
for _, n := range []string{"alpha", "beta", "gamma"} {
|
|
def.Params = append(def.Params, event.ParamDef{Name: n, SubscriptionKey: marked[n]})
|
|
}
|
|
return def
|
|
}
|
|
|
|
t.Run("no SubscriptionKey params returns EventKey verbatim", func(t *testing.T) {
|
|
def := makeDef()
|
|
got := ComputeSubscriptionID(def, map[string]string{"alpha": "x", "beta": "y"})
|
|
if got != "test.evt" {
|
|
t.Errorf("got %q, want %q", got, "test.evt")
|
|
}
|
|
})
|
|
|
|
t.Run("single SubscriptionKey param: non-sub params do not leak into ID", func(t *testing.T) {
|
|
def := makeDef("alpha")
|
|
id1 := ComputeSubscriptionID(def, map[string]string{"alpha": "value1", "beta": "ignored"})
|
|
id2 := ComputeSubscriptionID(def, map[string]string{"alpha": "value1", "beta": "different"})
|
|
if id1 != id2 {
|
|
t.Errorf("non-SubscriptionKey param change leaked into ID: %q vs %q", id1, id2)
|
|
}
|
|
})
|
|
|
|
t.Run("different SubscriptionKey value produces different ID", func(t *testing.T) {
|
|
def := makeDef("alpha")
|
|
id1 := ComputeSubscriptionID(def, map[string]string{"alpha": "v1"})
|
|
id2 := ComputeSubscriptionID(def, map[string]string{"alpha": "v2"})
|
|
if id1 == id2 {
|
|
t.Errorf("different values produced same ID: %q", id1)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestComputeSubscriptionID_Stability(t *testing.T) {
|
|
// Param order in the ParamDef list must not affect the result (sorted by name internally).
|
|
def1 := &event.KeyDefinition{
|
|
Key: "test.evt",
|
|
Params: []event.ParamDef{
|
|
{Name: "b", SubscriptionKey: true},
|
|
{Name: "a", SubscriptionKey: true},
|
|
},
|
|
}
|
|
def2 := &event.KeyDefinition{
|
|
Key: "test.evt",
|
|
Params: []event.ParamDef{
|
|
{Name: "a", SubscriptionKey: true},
|
|
{Name: "b", SubscriptionKey: true},
|
|
},
|
|
}
|
|
id1 := ComputeSubscriptionID(def1, map[string]string{"a": "1", "b": "2"})
|
|
id2 := ComputeSubscriptionID(def2, map[string]string{"a": "1", "b": "2"})
|
|
if id1 != id2 {
|
|
t.Errorf("order-sensitive: id1=%q id2=%q", id1, id2)
|
|
}
|
|
}
|
|
|
|
func TestComputeSubscriptionID_Format(t *testing.T) {
|
|
def := &event.KeyDefinition{
|
|
Key: "mail.user_mailbox.event.message_received_v1",
|
|
Params: []event.ParamDef{{Name: "mailbox", SubscriptionKey: true}},
|
|
}
|
|
id := ComputeSubscriptionID(def, map[string]string{"mailbox": "liuxinyang@example.com"})
|
|
prefix := "mail.user_mailbox.event.message_received_v1:"
|
|
if !strings.HasPrefix(id, prefix) {
|
|
t.Fatalf("missing prefix: %q", id)
|
|
}
|
|
suffix := strings.TrimPrefix(id, prefix)
|
|
if len(suffix) != 16 {
|
|
t.Errorf("fingerprint length = %d, want 16", len(suffix))
|
|
}
|
|
for _, c := range suffix {
|
|
isValid := (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'
|
|
if !isValid {
|
|
t.Errorf("non-base64URL char in fingerprint: %q", suffix)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestComputeSubscriptionID_UnicodeAndSpecialChars(t *testing.T) {
|
|
def := &event.KeyDefinition{
|
|
Key: "test.evt",
|
|
Params: []event.ParamDef{{Name: "value", SubscriptionKey: true}},
|
|
}
|
|
for _, val := range []string{"中文", "emoji🚀", "with spaces", "with:colons", "with\"quotes"} {
|
|
id := ComputeSubscriptionID(def, map[string]string{"value": val})
|
|
if !strings.HasPrefix(id, "test.evt:") || len(id) != len("test.evt:")+16 {
|
|
t.Errorf("ID malformed for value=%q: %q (len=%d)", val, id, len(id))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestComputeSubscriptionID_EmptyValue(t *testing.T) {
|
|
def := &event.KeyDefinition{
|
|
Key: "test.evt",
|
|
Params: []event.ParamDef{{Name: "x", SubscriptionKey: true}},
|
|
}
|
|
id1 := ComputeSubscriptionID(def, map[string]string{"x": ""})
|
|
id2 := ComputeSubscriptionID(def, map[string]string{}) // missing entirely
|
|
if id1 != id2 {
|
|
t.Errorf("empty value should be indistinguishable from missing: %q vs %q", id1, id2)
|
|
}
|
|
id3 := ComputeSubscriptionID(def, map[string]string{"x": "nonempty"})
|
|
if id1 == id3 {
|
|
t.Errorf("empty and nonempty produced same ID: %q", id1)
|
|
}
|
|
}
|