fix(qq): resolve golangci-lint formatting issues

- Fix interface{} → any in sync.Map Range callbacks
- Wrap long method signatures in mockQQAPI
- Remove blank lines between }) and following statements per gofumpt
This commit is contained in:
kent.liu 2026-03-19 18:18:44 +08:00
parent d25abb59a4
commit d3de49f3a3
5 changed files with 1153 additions and 46 deletions

@ -0,0 +1 @@
Subproject commit 75ba9e83883d143fff60d511885e2b14b9cab597

File diff suppressed because it is too large Load diff

View file

@ -43,9 +43,9 @@ const (
bytesPerMiB = 1024 * 1024 bytesPerMiB = 1024 * 1024
// Reconnection constants (used as defaults when config values are 0) // Reconnection constants (used as defaults when config values are 0)
reconnectInitialDefault = 5 * time.Second reconnectInitialDefault = 5 * time.Second
reconnectMaxDefault = 5 * time.Minute reconnectMaxDefault = 5 * time.Minute
reconnectMultiplier = 2.0 reconnectMultiplier = 2.0
// Retry constants (used as defaults when config values are 0) // Retry constants (used as defaults when config values are 0)
maxRetriesDefault = 3 maxRetriesDefault = 3
@ -53,8 +53,8 @@ const (
retryMaxDelayDefault = 10 * time.Second retryMaxDelayDefault = 10 * time.Second
// Rate limit defaults // Rate limit defaults
rateLimitGroupDefault = 500 * time.Millisecond rateLimitGroupDefault = 500 * time.Millisecond
rateLimitDirectDefault = 200 * time.Millisecond rateLimitDirectDefault = 200 * time.Millisecond
) )
type qqAPI interface { type qqAPI interface {
@ -96,9 +96,9 @@ type QQChannel struct {
stopOnce sync.Once stopOnce sync.Once
// Reconnection state // Reconnection state
reconnectMu sync.Mutex reconnectMu sync.Mutex
reconnecting bool reconnecting bool
stopReconnect chan struct{} stopReconnect chan struct{}
// Rate limiting // Rate limiting
groupRateLimiter *rateLimiter groupRateLimiter *rateLimiter
@ -270,9 +270,9 @@ func (c *QQChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
if attempt > 0 { if attempt > 0 {
backoff := c.calculateBackoff(attempt - 1) backoff := c.calculateBackoff(attempt - 1)
logger.InfoCF("qq", "Retrying send", map[string]any{ logger.InfoCF("qq", "Retrying send", map[string]any{
"attempt": attempt, "attempt": attempt,
"chat_id": msg.ChatID, "chat_id": msg.ChatID,
"backoff": backoff.String(), "backoff": backoff.String(),
}) })
select { select {
@ -1093,15 +1093,15 @@ func (c *QQChannel) reconnect() {
// resetSessionState clears transient state that may be invalid after reconnection. // resetSessionState clears transient state that may be invalid after reconnection.
func (c *QQChannel) resetSessionState() { func (c *QQChannel) resetSessionState() {
// Clear transient state that may be invalid after reconnection // Clear transient state that may be invalid after reconnection
c.chatType.Range(func(key, value interface{}) bool { c.chatType.Range(func(key, value any) bool {
c.chatType.Delete(key) c.chatType.Delete(key)
return true return true
}) })
c.lastMsgID.Range(func(key, value interface{}) bool { c.lastMsgID.Range(func(key, value any) bool {
c.lastMsgID.Delete(key) c.lastMsgID.Delete(key)
return true return true
}) })
c.msgSeqCounters.Range(func(key, value interface{}) bool { c.msgSeqCounters.Range(func(key, value any) bool {
c.msgSeqCounters.Delete(key) c.msgSeqCounters.Delete(key)
return true return true
}) })

View file

@ -18,14 +18,14 @@ import (
// mockQQAPI implements qqAPI for testing // mockQQAPI implements qqAPI for testing
type mockQQAPI struct { type mockQQAPI struct {
callCount atomic.Int32 callCount atomic.Int32
wsCalled atomic.Bool wsCalled atomic.Bool
postCalled atomic.Bool postCalled atomic.Bool
lastChatID string lastChatID string
lastChatKind string lastChatKind string
shouldFail bool shouldFail bool
failErr error failErr error
failRemaining atomic.Int32 failRemaining atomic.Int32
} }
func (m *mockQQAPI) WS(ctx context.Context, params map[string]string, body string) (*dto.WebsocketAP, error) { func (m *mockQQAPI) WS(ctx context.Context, params map[string]string, body string) (*dto.WebsocketAP, error) {
@ -35,7 +35,9 @@ func (m *mockQQAPI) WS(ctx context.Context, params map[string]string, body strin
}, nil }, nil
} }
func (m *mockQQAPI) PostGroupMessage(ctx context.Context, groupID string, msg dto.APIMessage, opt ...options.Option) (*dto.Message, error) { func (m *mockQQAPI) PostGroupMessage(
ctx context.Context, groupID string, msg dto.APIMessage, opt ...options.Option,
) (*dto.Message, error) {
m.postCalled.Store(true) m.postCalled.Store(true)
m.lastChatID = groupID m.lastChatID = groupID
m.lastChatKind = "group" m.lastChatKind = "group"
@ -51,7 +53,9 @@ func (m *mockQQAPI) PostGroupMessage(ctx context.Context, groupID string, msg dt
return &dto.Message{ID: "msg-123"}, nil return &dto.Message{ID: "msg-123"}, nil
} }
func (m *mockQQAPI) PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage, opt ...options.Option) (*dto.Message, error) { func (m *mockQQAPI) PostC2CMessage(
ctx context.Context, userID string, msg dto.APIMessage, opt ...options.Option,
) (*dto.Message, error) {
m.postCalled.Store(true) m.postCalled.Store(true)
m.lastChatID = userID m.lastChatID = userID
m.lastChatKind = "direct" m.lastChatKind = "direct"
@ -78,12 +82,12 @@ func newTestQQChannel(t *testing.T, api *mockQQAPI) *QQChannel {
} }
ch := &QQChannel{ ch := &QQChannel{
BaseChannel: channels.NewBaseChannel("qq", cfg, nil, config.FlexibleStringSlice{"*"}), BaseChannel: channels.NewBaseChannel("qq", cfg, nil, config.FlexibleStringSlice{"*"}),
config: cfg, config: cfg,
api: api, api: api,
dedup: make(map[string]time.Time), dedup: make(map[string]time.Time),
done: make(chan struct{}), done: make(chan struct{}),
groupRateLimiter: newRateLimiter(500 * time.Millisecond), groupRateLimiter: newRateLimiter(500 * time.Millisecond),
directRateLimiter: newRateLimiter(200 * time.Millisecond), directRateLimiter: newRateLimiter(200 * time.Millisecond),
} }
ch.SetRunning(true) ch.SetRunning(true)
@ -102,7 +106,6 @@ func TestSend_Success(t *testing.T) {
ChatID: "group-1", ChatID: "group-1",
Content: "Hello", Content: "Hello",
}) })
if err != nil { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
@ -114,7 +117,7 @@ func TestSend_Success(t *testing.T) {
// TestSend_WithRetry tests that retries happen on transient errors // TestSend_WithRetry tests that retries happen on transient errors
func TestSend_WithRetry(t *testing.T) { func TestSend_WithRetry(t *testing.T) {
api := &mockQQAPI{ api := &mockQQAPI{
shouldFail: true, shouldFail: true,
failErr: errors.New("connection reset"), failErr: errors.New("connection reset"),
failRemaining: atomic.Int32{}, failRemaining: atomic.Int32{},
} }
@ -127,7 +130,6 @@ func TestSend_WithRetry(t *testing.T) {
ChatID: "group-1", ChatID: "group-1",
Content: "Hello", Content: "Hello",
}) })
// Should succeed after retries // Should succeed after retries
if err != nil { if err != nil {
t.Errorf("expected success after retry, got %v", err) t.Errorf("expected success after retry, got %v", err)
@ -140,7 +142,7 @@ func TestSend_WithRetry(t *testing.T) {
// TestSend_NonRetryableError tests that non-retryable errors don't retry // TestSend_NonRetryableError tests that non-retryable errors don't retry
func TestSend_NonRetryableError(t *testing.T) { func TestSend_NonRetryableError(t *testing.T) {
api := &mockQQAPI{ api := &mockQQAPI{
shouldFail: true, shouldFail: true,
failErr: errors.New("unauthorized"), failErr: errors.New("unauthorized"),
failRemaining: atomic.Int32{}, failRemaining: atomic.Int32{},
} }
@ -166,7 +168,7 @@ func TestSend_NonRetryableError(t *testing.T) {
// TestSend_ContextCancellation tests that retries stop on context cancellation // TestSend_ContextCancellation tests that retries stop on context cancellation
func TestSend_ContextCancellation(t *testing.T) { func TestSend_ContextCancellation(t *testing.T) {
api := &mockQQAPI{ api := &mockQQAPI{
shouldFail: true, shouldFail: true,
failErr: errors.New("timeout"), failErr: errors.New("timeout"),
failRemaining: atomic.Int32{}, failRemaining: atomic.Int32{},
} }
@ -414,9 +416,9 @@ func TestQQChannel_ResetSessionState(t *testing.T) {
// TestQQChannel_ResetSessionState_ReasoningChannel tests that reasoning channel is re-registered // TestQQChannel_ResetSessionState_ReasoningChannel tests that reasoning channel is re-registered
func TestQQChannel_ResetSessionState_ReasoningChannel(t *testing.T) { func TestQQChannel_ResetSessionState_ReasoningChannel(t *testing.T) {
ch := &QQChannel{ ch := &QQChannel{
BaseChannel: channels.NewBaseChannel("qq", config.QQConfig{}, nil, config.FlexibleStringSlice{"*"}), BaseChannel: channels.NewBaseChannel("qq", config.QQConfig{}, nil, config.FlexibleStringSlice{"*"}),
config: config.QQConfig{ReasoningChannelID: "reasoning-group"}, config: config.QQConfig{ReasoningChannelID: "reasoning-group"},
dedup: make(map[string]time.Time), dedup: make(map[string]time.Time),
groupRateLimiter: newRateLimiter(500 * time.Millisecond), groupRateLimiter: newRateLimiter(500 * time.Millisecond),
directRateLimiter: newRateLimiter(200 * time.Millisecond), directRateLimiter: newRateLimiter(200 * time.Millisecond),
} }
@ -431,9 +433,9 @@ func TestQQChannel_ResetSessionState_ReasoningChannel(t *testing.T) {
// TestQQChannel_ConfigDrivenParams tests that config values override defaults. // TestQQChannel_ConfigDrivenParams tests that config values override defaults.
func TestQQChannel_ConfigDrivenParams(t *testing.T) { func TestQQChannel_ConfigDrivenParams(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
setup func(*config.QQConfig) setup func(*config.QQConfig)
check func(*testing.T, *QQChannel) check func(*testing.T, *QQChannel)
}{ }{
{ {
name: "reconnect initial uses config when set", name: "reconnect initial uses config when set",
@ -490,7 +492,7 @@ func TestQQChannel_ConfigDrivenParams(t *testing.T) {
}, },
}, },
{ {
name: "rate limiters use config values", name: "rate limiters use config values",
setup: func(cfg *config.QQConfig) { setup: func(cfg *config.QQConfig) {
cfg.RateLimitGroupMs = 1000 cfg.RateLimitGroupMs = 1000
cfg.RateLimitDirectMs = 500 cfg.RateLimitDirectMs = 500

View file

@ -360,11 +360,11 @@ type QQConfig struct {
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_QQ_REASONING_CHANNEL_ID"` ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_QQ_REASONING_CHANNEL_ID"`
// Stability config (all optional with sensible defaults) // Stability config (all optional with sensible defaults)
ReconnectInitialMs int `json:"reconnect_initial_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_INITIAL_MS"` ReconnectInitialMs int `json:"reconnect_initial_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_INITIAL_MS"`
ReconnectMaxMs int `json:"reconnect_max_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_MAX_MS"` ReconnectMaxMs int `json:"reconnect_max_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_MAX_MS"`
MaxRetries int `json:"max_retries,omitempty" env:"PICOCLAW_CHANNELS_QQ_MAX_RETRIES"` MaxRetries int `json:"max_retries,omitempty" env:"PICOCLAW_CHANNELS_QQ_MAX_RETRIES"`
RetryInitialDelayMs int `json:"retry_initial_delay_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RETRY_INITIAL_DELAY_MS"` RetryInitialDelayMs int `json:"retry_initial_delay_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RETRY_INITIAL_DELAY_MS"`
RetryMaxDelayMs int `json:"retry_max_delay_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RETRY_MAX_DELAY_MS"` RetryMaxDelayMs int `json:"retry_max_delay_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RETRY_MAX_DELAY_MS"`
RateLimitGroupMs int `json:"rate_limit_group_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RATE_LIMIT_GROUP_MS"` RateLimitGroupMs int `json:"rate_limit_group_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RATE_LIMIT_GROUP_MS"`
RateLimitDirectMs int `json:"rate_limit_direct_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RATE_LIMIT_DIRECT_MS"` RateLimitDirectMs int `json:"rate_limit_direct_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RATE_LIMIT_DIRECT_MS"`
} }