mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
fix(mcp): harden dashboard dispatch lifecycle
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type pingWriterFake struct {
|
||||
mu sync.Mutex
|
||||
writeCalls int
|
||||
writeErr error
|
||||
writeStarted chan struct{}
|
||||
continueWrite chan struct{}
|
||||
}
|
||||
|
||||
func (writer *pingWriterFake) Close() error { return nil }
|
||||
|
||||
type permanentlyBlockedPingWriter struct {
|
||||
writeStarted chan struct{}
|
||||
transportClosed chan struct{}
|
||||
}
|
||||
|
||||
func (writer *permanentlyBlockedPingWriter) WriteMessage(int, []byte) error {
|
||||
close(writer.writeStarted)
|
||||
<-writer.transportClosed
|
||||
return errors.New("transport closed")
|
||||
}
|
||||
|
||||
func (writer *permanentlyBlockedPingWriter) Close() error {
|
||||
close(writer.transportClosed)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (writer *pingWriterFake) WriteMessage(int, []byte) error {
|
||||
writer.mu.Lock()
|
||||
writer.writeCalls++
|
||||
writer.mu.Unlock()
|
||||
if writer.writeStarted != nil {
|
||||
close(writer.writeStarted)
|
||||
<-writer.continueWrite
|
||||
}
|
||||
return writer.writeErr
|
||||
}
|
||||
|
||||
func (writer *pingWriterFake) calls() int {
|
||||
writer.mu.Lock()
|
||||
defer writer.mu.Unlock()
|
||||
return writer.writeCalls
|
||||
}
|
||||
|
||||
func TestWebsocketPingLoop_stopsAndJoinsWithoutWritingAfterStop(t *testing.T) {
|
||||
// Given
|
||||
ticks := make(chan time.Time, 1)
|
||||
writer := &pingWriterFake{writeStarted: make(chan struct{}), continueWrite: make(chan struct{})}
|
||||
stop := startWebsocketPing(context.Background(), ticks, writer)
|
||||
|
||||
// When
|
||||
ticks <- time.Time{}
|
||||
<-writer.writeStarted
|
||||
close(writer.continueWrite)
|
||||
stop()
|
||||
ticks <- time.Time{}
|
||||
|
||||
// Then
|
||||
require.Equal(t, 1, writer.calls())
|
||||
}
|
||||
|
||||
func TestWebsocketPingLoop_exitsOnWriteError(t *testing.T) {
|
||||
// Given
|
||||
ticks := make(chan time.Time, 1)
|
||||
writer := &pingWriterFake{writeErr: errors.New("closed")}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- websocketPingLoop(context.Background(), ticks, writer) }()
|
||||
|
||||
// When
|
||||
ticks <- time.Time{}
|
||||
|
||||
// Then
|
||||
require.Error(t, <-done)
|
||||
ticks <- time.Time{}
|
||||
require.Equal(t, 1, writer.calls())
|
||||
}
|
||||
|
||||
func TestWebsocketPingLoop_cleanupOverlapsTickAndJoinsWriter(t *testing.T) {
|
||||
// Given
|
||||
ticks := make(chan time.Time, 1)
|
||||
writer := &pingWriterFake{
|
||||
writeStarted: make(chan struct{}),
|
||||
continueWrite: make(chan struct{}),
|
||||
}
|
||||
stop := startWebsocketPing(context.Background(), ticks, writer)
|
||||
ticks <- time.Time{}
|
||||
<-writer.writeStarted
|
||||
|
||||
// When
|
||||
stopped := make(chan struct{})
|
||||
go func() {
|
||||
stop()
|
||||
close(stopped)
|
||||
}()
|
||||
select {
|
||||
case <-stopped:
|
||||
require.Fail(t, "ping worker stop returned before the in-flight write joined")
|
||||
default:
|
||||
}
|
||||
close(writer.continueWrite)
|
||||
<-stopped
|
||||
ticks <- time.Time{}
|
||||
|
||||
// Then
|
||||
require.Equal(t, 1, writer.calls())
|
||||
}
|
||||
|
||||
func TestWebsocketPingStop_unblocksPermanentlyBlockedWriteBeforeJoin(t *testing.T) {
|
||||
// Given
|
||||
ticks := make(chan time.Time, 1)
|
||||
writer := &permanentlyBlockedPingWriter{
|
||||
writeStarted: make(chan struct{}),
|
||||
transportClosed: make(chan struct{}),
|
||||
}
|
||||
stop := startWebsocketPing(context.Background(), ticks, writer)
|
||||
ticks <- time.Time{}
|
||||
<-writer.writeStarted
|
||||
|
||||
// When
|
||||
stopped := make(chan struct{})
|
||||
go func() {
|
||||
stop()
|
||||
close(stopped)
|
||||
}()
|
||||
|
||||
// Then
|
||||
deadline := time.NewTimer(time.Second)
|
||||
defer deadline.Stop()
|
||||
select {
|
||||
case <-writer.transportClosed:
|
||||
case <-deadline.C:
|
||||
require.Fail(t, "stop did not close the blocked ping transport")
|
||||
}
|
||||
select {
|
||||
case <-stopped:
|
||||
case <-deadline.C:
|
||||
require.Fail(t, "stop did not join after closing the blocked ping transport")
|
||||
}
|
||||
}
|
||||
|
||||
var _ websocketPingWriter = (*pingWriterFake)(nil)
|
||||
Reference in New Issue
Block a user