95 lines
3.7 KiB
Go
95 lines
3.7 KiB
Go
package rocketmqx
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"strings"
|
||
"testing"
|
||
|
||
"hyapp/pkg/logx"
|
||
|
||
"github.com/apache/rocketmq-client-go/v2/consumer"
|
||
"github.com/apache/rocketmq-client-go/v2/primitive"
|
||
)
|
||
|
||
type fakePushConsumer struct {
|
||
callback func(context.Context, ...*primitive.MessageExt) (consumer.ConsumeResult, error)
|
||
}
|
||
|
||
func (f *fakePushConsumer) Start() error { return nil }
|
||
func (f *fakePushConsumer) Shutdown() error { return nil }
|
||
func (f *fakePushConsumer) Subscribe(_ string, _ consumer.MessageSelector, callback func(context.Context, ...*primitive.MessageExt) (consumer.ConsumeResult, error)) error {
|
||
f.callback = callback
|
||
return nil
|
||
}
|
||
func (f *fakePushConsumer) Unsubscribe(string) error { return nil }
|
||
func (f *fakePushConsumer) Suspend() {}
|
||
func (f *fakePushConsumer) Resume() {}
|
||
func (f *fakePushConsumer) GetOffsetDiffMap() map[string]int64 { return nil }
|
||
|
||
func TestSubscribeHandlerErrorReturnsRetryLaterWithoutSDKError(t *testing.T) {
|
||
var output bytes.Buffer
|
||
if err := logx.Init(logx.Config{Service: "rocketmqx-test", Env: "test", Format: "json", Output: &output}); err != nil {
|
||
t.Fatalf("init test logger: %v", err)
|
||
}
|
||
|
||
client := &fakePushConsumer{}
|
||
consumerWrapper := &Consumer{client: client, group: "group-safe-log"}
|
||
handlerErr := errors.New("projection unavailable")
|
||
if err := consumerWrapper.Subscribe("topic-safe-log", "tag-safe-log", func(context.Context, ConsumedMessage) error {
|
||
return handlerErr
|
||
}); err != nil {
|
||
t.Fatalf("subscribe: %v", err)
|
||
}
|
||
if client.callback == nil {
|
||
t.Fatal("subscribe callback was not captured")
|
||
}
|
||
|
||
extended := &primitive.MessageExt{MsgId: "msg-safe-log", ReconsumeTimes: 3}
|
||
// 直接初始化嵌入的 Message,避免复制其 RWMutex;Queue 仍故意保持 nil,确保日志路径
|
||
// 一旦把 MessageExt 交给 String 格式化就会在测试中立即暴露。
|
||
extended.Topic = "topic-safe-log"
|
||
extended.Body = []byte("sensitive-body-must-not-be-logged")
|
||
extended.WithTag("tag-safe-log")
|
||
extended.WithKeys([]string{"event-safe-log"})
|
||
extended.WithProperty("private-property", "secret-value-must-not-be-logged")
|
||
// Queue 故意保持 nil:MessageExt.String 会解引用 Queue 并 panic。callback 能完成并写日志,
|
||
// 因而同时锁定包装层只记录复制后的安全标量,绝不把 MessageExt 交给格式化器。
|
||
result, callbackErr := client.callback(context.Background(), extended)
|
||
if result != consumer.ConsumeRetryLater {
|
||
t.Fatalf("consume result = %v, want ConsumeRetryLater", result)
|
||
}
|
||
if callbackErr != nil {
|
||
t.Fatalf("callback error = %v, want nil so SDK does not format the shared message batch", callbackErr)
|
||
}
|
||
|
||
rawLog := strings.TrimSpace(output.String())
|
||
var record map[string]any
|
||
if err := json.Unmarshal([]byte(rawLog), &record); err != nil {
|
||
t.Fatalf("decode safe consume log %q: %v", rawLog, err)
|
||
}
|
||
for key, want := range map[string]string{
|
||
"msg": "rocketmq_consume_handler_failed",
|
||
"consumer_group": "group-safe-log",
|
||
"topic": "topic-safe-log",
|
||
"tag": "tag-safe-log",
|
||
"keys": "event-safe-log",
|
||
"msg_id": "msg-safe-log",
|
||
"error": handlerErr.Error(),
|
||
} {
|
||
if got := record[key]; got != want {
|
||
t.Errorf("log field %s = %#v, want %q", key, got, want)
|
||
}
|
||
}
|
||
if got := record["reconsume_times"]; got != float64(3) {
|
||
t.Errorf("log field reconsume_times = %#v, want 3", got)
|
||
}
|
||
for _, forbidden := range []string{"sensitive-body-must-not-be-logged", "private-property", "secret-value-must-not-be-logged"} {
|
||
if strings.Contains(rawLog, forbidden) {
|
||
t.Errorf("safe consume log leaked forbidden message data %q: %s", forbidden, rawLog)
|
||
}
|
||
}
|
||
}
|