mirror of
https://github.com/larksuite/cli.git
synced 2026-07-06 00:06:28 +08:00
502 lines
15 KiB
Go
502 lines
15 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package event
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// --- im.message.message_read_v1 ---
|
|
|
|
func TestImMessageReadProcessor_Compact(t *testing.T) {
|
|
p := &ImMessageReadProcessor{}
|
|
if p.EventType() != "im.message.message_read_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.message.message_read_v1", `{
|
|
"reader": {
|
|
"reader_id": {"open_id": "ou_reader"},
|
|
"read_time": "1700000001"
|
|
},
|
|
"message_id_list": ["msg_001", "msg_002"]
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["type"] != "im.message.message_read_v1" {
|
|
t.Errorf("type = %v", result["type"])
|
|
}
|
|
if result["reader_id"] != "ou_reader" {
|
|
t.Errorf("reader_id = %v", result["reader_id"])
|
|
}
|
|
if result["read_time"] != "1700000001" {
|
|
t.Errorf("read_time = %v", result["read_time"])
|
|
}
|
|
ids, ok := result["message_ids"].([]string)
|
|
if !ok || len(ids) != 2 {
|
|
t.Errorf("message_ids = %v", result["message_ids"])
|
|
}
|
|
}
|
|
|
|
func TestImMessageReadProcessor_Raw(t *testing.T) {
|
|
p := &ImMessageReadProcessor{}
|
|
raw := makeRawEvent("im.message.message_read_v1", `{}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformRaw).(*RawEvent)
|
|
if !ok {
|
|
t.Fatal("raw mode should return *RawEvent")
|
|
}
|
|
if result.Header.EventType != "im.message.message_read_v1" {
|
|
t.Errorf("EventType = %v", result.Header.EventType)
|
|
}
|
|
}
|
|
|
|
func TestImMessageReadProcessor_UnmarshalError(t *testing.T) {
|
|
p := &ImMessageReadProcessor{}
|
|
raw := makeRawEvent("im.message.message_read_v1", `not json`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(*RawEvent)
|
|
if !ok {
|
|
t.Fatal("unmarshal error should fallback to *RawEvent")
|
|
}
|
|
if result.Header.EventType != "im.message.message_read_v1" {
|
|
t.Errorf("EventType = %v", result.Header.EventType)
|
|
}
|
|
}
|
|
|
|
func TestImMessageReadProcessor_Dedup(t *testing.T) {
|
|
p := &ImMessageReadProcessor{}
|
|
raw := makeRawEvent("im.message.message_read_v1", `{}`)
|
|
if k := p.DeduplicateKey(raw); k != "ev_test" {
|
|
t.Errorf("DeduplicateKey = %q", k)
|
|
}
|
|
}
|
|
|
|
// --- im.message.reaction.created_v1 / deleted_v1 ---
|
|
|
|
func TestImReactionCreatedProcessor_Compact(t *testing.T) {
|
|
p := NewImReactionCreatedProcessor()
|
|
if p.EventType() != "im.message.reaction.created_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.message.reaction.created_v1", `{
|
|
"message_id": "msg_react",
|
|
"reaction_type": {"emoji_type": "THUMBSUP"},
|
|
"operator_type": "user",
|
|
"user_id": {"open_id": "ou_reactor"},
|
|
"action_time": "1700000002"
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["action"] != "added" {
|
|
t.Errorf("action = %v, want added", result["action"])
|
|
}
|
|
if result["message_id"] != "msg_react" {
|
|
t.Errorf("message_id = %v", result["message_id"])
|
|
}
|
|
if result["emoji_type"] != "THUMBSUP" {
|
|
t.Errorf("emoji_type = %v", result["emoji_type"])
|
|
}
|
|
if result["operator_id"] != "ou_reactor" {
|
|
t.Errorf("operator_id = %v", result["operator_id"])
|
|
}
|
|
if result["action_time"] != "1700000002" {
|
|
t.Errorf("action_time = %v", result["action_time"])
|
|
}
|
|
}
|
|
|
|
func TestImReactionDeletedProcessor_Compact(t *testing.T) {
|
|
p := NewImReactionDeletedProcessor()
|
|
if p.EventType() != "im.message.reaction.deleted_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.message.reaction.deleted_v1", `{
|
|
"message_id": "msg_unreact",
|
|
"reaction_type": {"emoji_type": "THUMBSUP"},
|
|
"user_id": {"open_id": "ou_reactor"},
|
|
"action_time": "1700000003"
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["action"] != "removed" {
|
|
t.Errorf("action = %v, want removed", result["action"])
|
|
}
|
|
}
|
|
|
|
func TestImReactionProcessor_Raw(t *testing.T) {
|
|
p := NewImReactionCreatedProcessor()
|
|
raw := makeRawEvent("im.message.reaction.created_v1", `{}`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformRaw).(*RawEvent); !ok {
|
|
t.Fatal("raw mode should return *RawEvent")
|
|
}
|
|
}
|
|
|
|
func TestImReactionProcessor_UnmarshalError(t *testing.T) {
|
|
p := NewImReactionCreatedProcessor()
|
|
raw := makeRawEvent("im.message.reaction.created_v1", `bad`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformCompact).(*RawEvent); !ok {
|
|
t.Fatal("unmarshal error should fallback to *RawEvent")
|
|
}
|
|
}
|
|
|
|
// --- im.chat.member.bot.added_v1 / deleted_v1 ---
|
|
|
|
func TestImChatBotAddedProcessor_Compact(t *testing.T) {
|
|
p := NewImChatBotAddedProcessor()
|
|
if p.EventType() != "im.chat.member.bot.added_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.chat.member.bot.added_v1", `{
|
|
"chat_id": "oc_bot",
|
|
"operator_id": {"open_id": "ou_operator"},
|
|
"external": false
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["action"] != "added" {
|
|
t.Errorf("action = %v", result["action"])
|
|
}
|
|
if result["chat_id"] != "oc_bot" {
|
|
t.Errorf("chat_id = %v", result["chat_id"])
|
|
}
|
|
if result["operator_id"] != "ou_operator" {
|
|
t.Errorf("operator_id = %v", result["operator_id"])
|
|
}
|
|
if result["external"] != false {
|
|
t.Errorf("external = %v", result["external"])
|
|
}
|
|
}
|
|
|
|
func TestImChatBotDeletedProcessor_Compact(t *testing.T) {
|
|
p := NewImChatBotDeletedProcessor()
|
|
if p.EventType() != "im.chat.member.bot.deleted_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.chat.member.bot.deleted_v1", `{
|
|
"chat_id": "oc_bot2",
|
|
"operator_id": {"open_id": "ou_op2"},
|
|
"external": true
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["action"] != "removed" {
|
|
t.Errorf("action = %v, want removed", result["action"])
|
|
}
|
|
if result["external"] != true {
|
|
t.Errorf("external = %v, want true", result["external"])
|
|
}
|
|
}
|
|
|
|
func TestImChatBotProcessor_Raw(t *testing.T) {
|
|
p := NewImChatBotAddedProcessor()
|
|
raw := makeRawEvent("im.chat.member.bot.added_v1", `{}`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformRaw).(*RawEvent); !ok {
|
|
t.Fatal("raw mode should return *RawEvent")
|
|
}
|
|
}
|
|
|
|
func TestImChatBotProcessor_UnmarshalError(t *testing.T) {
|
|
p := NewImChatBotAddedProcessor()
|
|
raw := makeRawEvent("im.chat.member.bot.added_v1", `{bad}`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformCompact).(*RawEvent); !ok {
|
|
t.Fatal("unmarshal error should fallback to *RawEvent")
|
|
}
|
|
}
|
|
|
|
// --- im.chat.member.user.added_v1 / withdrawn_v1 / deleted_v1 ---
|
|
|
|
func TestImChatMemberUserAddedProcessor_Compact(t *testing.T) {
|
|
p := NewImChatMemberUserAddedProcessor()
|
|
if p.EventType() != "im.chat.member.user.added_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.chat.member.user.added_v1", `{
|
|
"chat_id": "oc_members",
|
|
"operator_id": {"open_id": "ou_admin"},
|
|
"external": false,
|
|
"users": [
|
|
{"user_id": {"open_id": "ou_user1"}, "name": "Alice"},
|
|
{"user_id": {"open_id": "ou_user2"}, "name": "Bob"}
|
|
]
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["action"] != "added" {
|
|
t.Errorf("action = %v", result["action"])
|
|
}
|
|
if result["chat_id"] != "oc_members" {
|
|
t.Errorf("chat_id = %v", result["chat_id"])
|
|
}
|
|
if result["operator_id"] != "ou_admin" {
|
|
t.Errorf("operator_id = %v", result["operator_id"])
|
|
}
|
|
userIDs, ok := result["user_ids"].([]string)
|
|
if !ok || len(userIDs) != 2 {
|
|
t.Fatalf("user_ids = %v", result["user_ids"])
|
|
}
|
|
if userIDs[0] != "ou_user1" || userIDs[1] != "ou_user2" {
|
|
t.Errorf("user_ids = %v", userIDs)
|
|
}
|
|
}
|
|
|
|
func TestImChatMemberUserWithdrawnProcessor_Compact(t *testing.T) {
|
|
p := NewImChatMemberUserWithdrawnProcessor()
|
|
if p.EventType() != "im.chat.member.user.withdrawn_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.chat.member.user.withdrawn_v1", `{
|
|
"chat_id": "oc_w",
|
|
"operator_id": {"open_id": "ou_self"},
|
|
"external": false,
|
|
"users": [{"user_id": {"open_id": "ou_self"}, "name": "Self"}]
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["action"] != "withdrawn" {
|
|
t.Errorf("action = %v, want withdrawn", result["action"])
|
|
}
|
|
}
|
|
|
|
func TestImChatMemberUserDeletedProcessor_Compact(t *testing.T) {
|
|
p := NewImChatMemberUserDeletedProcessor()
|
|
if p.EventType() != "im.chat.member.user.deleted_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.chat.member.user.deleted_v1", `{
|
|
"chat_id": "oc_del",
|
|
"operator_id": {"open_id": "ou_admin"},
|
|
"users": [{"user_id": {"open_id": "ou_kicked"}}]
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["action"] != "removed" {
|
|
t.Errorf("action = %v, want removed", result["action"])
|
|
}
|
|
}
|
|
|
|
func TestImChatMemberUserProcessor_Raw(t *testing.T) {
|
|
p := NewImChatMemberUserAddedProcessor()
|
|
raw := makeRawEvent("im.chat.member.user.added_v1", `{}`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformRaw).(*RawEvent); !ok {
|
|
t.Fatal("raw mode should return *RawEvent")
|
|
}
|
|
}
|
|
|
|
func TestImChatMemberUserProcessor_UnmarshalError(t *testing.T) {
|
|
p := NewImChatMemberUserAddedProcessor()
|
|
raw := makeRawEvent("im.chat.member.user.added_v1", `bad json`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformCompact).(*RawEvent); !ok {
|
|
t.Fatal("unmarshal error should fallback to *RawEvent")
|
|
}
|
|
}
|
|
|
|
// --- im.chat.updated_v1 ---
|
|
|
|
func TestImChatUpdatedProcessor_Compact(t *testing.T) {
|
|
p := &ImChatUpdatedProcessor{}
|
|
if p.EventType() != "im.chat.updated_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.chat.updated_v1", `{
|
|
"chat_id": "oc_updated",
|
|
"operator_id": {"open_id": "ou_updater"},
|
|
"external": false,
|
|
"after_change": {"name": "New Name"},
|
|
"before_change": {"name": "Old Name"}
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["type"] != "im.chat.updated_v1" {
|
|
t.Errorf("type = %v", result["type"])
|
|
}
|
|
if result["chat_id"] != "oc_updated" {
|
|
t.Errorf("chat_id = %v", result["chat_id"])
|
|
}
|
|
if result["operator_id"] != "ou_updater" {
|
|
t.Errorf("operator_id = %v", result["operator_id"])
|
|
}
|
|
after, ok := result["after_change"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("after_change should be a map")
|
|
}
|
|
if after["name"] != "New Name" {
|
|
t.Errorf("after_change.name = %v", after["name"])
|
|
}
|
|
before, ok := result["before_change"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("before_change should be a map")
|
|
}
|
|
if before["name"] != "Old Name" {
|
|
t.Errorf("before_change.name = %v", before["name"])
|
|
}
|
|
}
|
|
|
|
func TestImChatUpdatedProcessor_Raw(t *testing.T) {
|
|
p := &ImChatUpdatedProcessor{}
|
|
raw := makeRawEvent("im.chat.updated_v1", `{}`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformRaw).(*RawEvent); !ok {
|
|
t.Fatal("raw mode should return *RawEvent")
|
|
}
|
|
}
|
|
|
|
func TestImChatUpdatedProcessor_UnmarshalError(t *testing.T) {
|
|
p := &ImChatUpdatedProcessor{}
|
|
raw := makeRawEvent("im.chat.updated_v1", `???`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformCompact).(*RawEvent); !ok {
|
|
t.Fatal("unmarshal error should fallback to *RawEvent")
|
|
}
|
|
}
|
|
|
|
// --- im.chat.disbanded_v1 ---
|
|
|
|
func TestImChatDisbandedProcessor_Compact(t *testing.T) {
|
|
p := &ImChatDisbandedProcessor{}
|
|
if p.EventType() != "im.chat.disbanded_v1" {
|
|
t.Fatalf("EventType = %q", p.EventType())
|
|
}
|
|
raw := makeRawEvent("im.chat.disbanded_v1", `{
|
|
"chat_id": "oc_disbanded",
|
|
"operator_id": {"open_id": "ou_disbander"},
|
|
"external": true
|
|
}`)
|
|
result, ok := p.Transform(context.Background(), raw, TransformCompact).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("compact should return map")
|
|
}
|
|
if result["type"] != "im.chat.disbanded_v1" {
|
|
t.Errorf("type = %v", result["type"])
|
|
}
|
|
if result["chat_id"] != "oc_disbanded" {
|
|
t.Errorf("chat_id = %v", result["chat_id"])
|
|
}
|
|
if result["operator_id"] != "ou_disbander" {
|
|
t.Errorf("operator_id = %v", result["operator_id"])
|
|
}
|
|
if result["external"] != true {
|
|
t.Errorf("external = %v, want true", result["external"])
|
|
}
|
|
}
|
|
|
|
func TestImChatDisbandedProcessor_Raw(t *testing.T) {
|
|
p := &ImChatDisbandedProcessor{}
|
|
raw := makeRawEvent("im.chat.disbanded_v1", `{}`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformRaw).(*RawEvent); !ok {
|
|
t.Fatal("raw mode should return *RawEvent")
|
|
}
|
|
}
|
|
|
|
func TestImChatDisbandedProcessor_UnmarshalError(t *testing.T) {
|
|
p := &ImChatDisbandedProcessor{}
|
|
raw := makeRawEvent("im.chat.disbanded_v1", `nope`)
|
|
if _, ok := p.Transform(context.Background(), raw, TransformCompact).(*RawEvent); !ok {
|
|
t.Fatal("unmarshal error should fallback to *RawEvent")
|
|
}
|
|
}
|
|
|
|
// --- Registry: all IM processors registered ---
|
|
|
|
func TestRegistryAllIMProcessors(t *testing.T) {
|
|
r := DefaultRegistry()
|
|
imTypes := []string{
|
|
"im.message.receive_v1",
|
|
"im.message.message_read_v1",
|
|
"im.message.reaction.created_v1",
|
|
"im.message.reaction.deleted_v1",
|
|
"im.chat.member.bot.added_v1",
|
|
"im.chat.member.bot.deleted_v1",
|
|
"im.chat.member.user.added_v1",
|
|
"im.chat.member.user.withdrawn_v1",
|
|
"im.chat.member.user.deleted_v1",
|
|
"im.chat.updated_v1",
|
|
"im.chat.disbanded_v1",
|
|
}
|
|
for _, et := range imTypes {
|
|
p := r.Lookup(et)
|
|
if p.EventType() != et {
|
|
t.Errorf("Lookup(%q) returned processor with EventType=%q", et, p.EventType())
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Helper: openID ---
|
|
|
|
func TestOpenID(t *testing.T) {
|
|
if id := openID(map[string]interface{}{"open_id": "ou_x"}); id != "ou_x" {
|
|
t.Errorf("got %q", id)
|
|
}
|
|
if id := openID("not a map"); id != "" {
|
|
t.Errorf("non-map should return empty, got %q", id)
|
|
}
|
|
if id := openID(nil); id != "" {
|
|
t.Errorf("nil should return empty, got %q", id)
|
|
}
|
|
}
|
|
|
|
// --- Helper: extractUserIDs ---
|
|
|
|
func TestExtractUserIDs(t *testing.T) {
|
|
users := []interface{}{
|
|
map[string]interface{}{
|
|
"user_id": map[string]interface{}{"open_id": "ou_a"},
|
|
"name": "Alice",
|
|
},
|
|
map[string]interface{}{
|
|
"user_id": map[string]interface{}{"open_id": "ou_b"},
|
|
},
|
|
"not a map",
|
|
map[string]interface{}{
|
|
"user_id": "not nested",
|
|
},
|
|
}
|
|
ids := extractUserIDs(users)
|
|
if len(ids) != 2 || ids[0] != "ou_a" || ids[1] != "ou_b" {
|
|
t.Errorf("extractUserIDs = %v, want [ou_a, ou_b]", ids)
|
|
}
|
|
}
|
|
|
|
func TestExtractUserIDs_Empty(t *testing.T) {
|
|
ids := extractUserIDs(nil)
|
|
if len(ids) != 0 {
|
|
t.Errorf("nil input should return empty, got %v", ids)
|
|
}
|
|
}
|
|
|
|
// --- WindowStrategy for all new processors ---
|
|
|
|
func TestWindowStrategy_IMProcessors(t *testing.T) {
|
|
processors := []EventProcessor{
|
|
&ImMessageReadProcessor{},
|
|
NewImReactionCreatedProcessor(),
|
|
NewImReactionDeletedProcessor(),
|
|
NewImChatBotAddedProcessor(),
|
|
NewImChatBotDeletedProcessor(),
|
|
NewImChatMemberUserAddedProcessor(),
|
|
NewImChatMemberUserWithdrawnProcessor(),
|
|
NewImChatMemberUserDeletedProcessor(),
|
|
&ImChatUpdatedProcessor{},
|
|
&ImChatDisbandedProcessor{},
|
|
}
|
|
for _, p := range processors {
|
|
if p.WindowStrategy() != (WindowConfig{}) {
|
|
t.Errorf("%s: WindowStrategy should return zero WindowConfig", p.EventType())
|
|
}
|
|
}
|
|
}
|