优化结构

This commit is contained in:
Yvan
2026-08-22 10:49:48 +08:00
parent 2b78b195c1
commit 54f76bd913
11 changed files with 501 additions and 98 deletions
+64 -3
View File
@@ -2,6 +2,7 @@ package mq
import (
"context"
"errors"
"log/slog"
"testing"
"time"
@@ -11,14 +12,19 @@ import (
)
type fakeClient struct {
subscribed []string
unsubscribed []string
handlers map[string]platformmq.Handler
subscribed []string
unsubscribed []string
handlers map[string]platformmq.Handler
subscribeErrors map[string]error
unsubscribeErrors map[string]error
}
func (*fakeClient) Publish(context.Context, string, []byte, byte, bool) error { return nil }
func (f *fakeClient) Subscribe(_ context.Context, topic string, _ byte, handler platformmq.Handler) error {
f.subscribed = append(f.subscribed, topic)
if err := f.subscribeErrors[topic]; err != nil {
return err
}
if f.handlers == nil {
f.handlers = make(map[string]platformmq.Handler)
}
@@ -27,6 +33,11 @@ func (f *fakeClient) Subscribe(_ context.Context, topic string, _ byte, handler
}
func (f *fakeClient) Unsubscribe(_ context.Context, topics ...string) error {
f.unsubscribed = append(f.unsubscribed, topics...)
for _, topic := range topics {
if err := f.unsubscribeErrors[topic]; err != nil {
return err
}
}
return nil
}
func (*fakeClient) Connected() bool { return true }
@@ -148,3 +159,53 @@ func TestReloadableDispatchesSameTopicToMultipleOwners(t *testing.T) {
t.Fatal("audit handler was not called")
}
}
func TestReloadablePersistsPartialBindingChangesWhenReconcileFails(t *testing.T) {
client := &fakeClient{subscribeErrors: map[string]error{"events.new": errors.New("subscribe failed")}}
r := &Reloadable{
clients: map[string]platformmq.Client{ProviderEMQX: client},
subscriptions: map[string]map[string]map[string]subscription{
ProviderEMQX: {
"events.keep": {"owner": {qos: platformmq.AtMostOnce, handler: func(context.Context, platformmq.Message) {}}},
"events.new": {"owner": {qos: platformmq.AtMostOnce, handler: func(context.Context, platformmq.Message) {}}},
},
},
bindings: map[string]map[string]byte{ProviderEMQX: {
"events.old": platformmq.AtMostOnce,
"events.keep": platformmq.AtMostOnce,
}},
pending: make(map[string]bool),
nextRetry: make(map[string]time.Time),
}
if err := r.reconcileProviderLocked(ProviderEMQX); err == nil {
t.Fatal("reconcileProviderLocked() error = nil, want subscribe failure")
}
if _, exists := r.bindings[ProviderEMQX]["events.old"]; exists {
t.Fatal("successfully removed binding remained after failed reconcile")
}
if _, exists := r.bindings[ProviderEMQX]["events.keep"]; !exists {
t.Fatal("unchanged binding was lost after failed reconcile")
}
if _, exists := r.bindings[ProviderEMQX]["events.new"]; exists {
t.Fatal("failed subscription was recorded as bound")
}
}
func TestReloadableLegacySubscribeDoesNotLeaveOfflineDeclaration(t *testing.T) {
r := &Reloadable{
clients: make(map[string]platformmq.Client),
configs: map[string]runtimeconfig.Config{ProviderEMQX: {Kind: "mq", Provider: ProviderEMQX, Enabled: true}},
subscriptions: make(map[string]map[string]map[string]subscription),
bindings: make(map[string]map[string]byte),
pending: make(map[string]bool),
nextRetry: make(map[string]time.Time),
}
err := r.Subscribe(context.Background(), "events.offline", platformmq.AtLeastOnce, func(context.Context, platformmq.Message) {})
if !errors.Is(err, platformmq.ErrUnavailable) {
t.Fatalf("Subscribe() error = %v, want ErrUnavailable", err)
}
if len(r.subscriptions[ProviderEMQX]) != 0 {
t.Fatalf("offline legacy declaration remained: %#v", r.subscriptions[ProviderEMQX])
}
}