+86
@@ -48,6 +48,17 @@ func (testObserver) OnRunnerFinished(context.Context, RunnerFinishedEvent) {}
|
||||
func (testObserver) OnPollingRetry(context.Context, PollingRetryEvent) {}
|
||||
func (testObserver) OnError(context.Context, ErrorEvent) {}
|
||||
|
||||
type blockingObserver struct {
|
||||
testObserver
|
||||
started chan struct{}
|
||||
release chan struct{}
|
||||
}
|
||||
|
||||
func (o *blockingObserver) OnError(context.Context, ErrorEvent) {
|
||||
close(o.started)
|
||||
<-o.release
|
||||
}
|
||||
|
||||
func TestGetUpdateTypesReturnsCopy(t *testing.T) {
|
||||
bot := &Bot[NoData]{updateTypes: []tgapi.UpdateType{tgapi.UpdateTypeMessage}}
|
||||
|
||||
@@ -308,6 +319,37 @@ func TestAddDatabaseLoggerWriterSkipsWhenAppDataIsNil(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerConfigurationRejectsNilAndLateMutation(t *testing.T) {
|
||||
original := sneklog.NewLogger()
|
||||
replacement := sneklog.NewLogger()
|
||||
bot := &Bot[NoData]{logger: original, token: "secret"}
|
||||
|
||||
bot.SetLogger(nil)
|
||||
if bot.logger != original {
|
||||
t.Fatal("SetLogger(nil) replaced the logger")
|
||||
}
|
||||
bot.SetAppData(NoData{})
|
||||
bot.AddAppDataLoggerWriter(nil)
|
||||
|
||||
if err := bot.beginRun(); err != nil {
|
||||
t.Fatalf("beginRun returned error: %v", err)
|
||||
}
|
||||
defer bot.finishRun()
|
||||
bot.SetLogger(replacement)
|
||||
bot.SetRequestLogger(replacement)
|
||||
bot.SetWebhookLogger(replacement)
|
||||
if bot.logger != original || bot.requestLogger != nil || bot.webhookLogger != nil {
|
||||
t.Fatal("logger configuration changed after runtime freeze")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAppDataLoggerWriterRejectsNilResult(t *testing.T) {
|
||||
bot := &Bot[NoData]{logger: sneklog.NewLogger(), hasAppData: true}
|
||||
defer func() { _ = bot.logger.Close() }()
|
||||
|
||||
bot.AddAppDataLoggerWriter(func(NoData) sneklog.LoggerWriter { return nil })
|
||||
}
|
||||
|
||||
func TestShouldWarnOnValueAppData(t *testing.T) {
|
||||
type testDB struct{}
|
||||
type dbIface interface{ Ping() error }
|
||||
@@ -378,6 +420,50 @@ func TestSetObserverNilClearsObserver(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeObserverDispatchIsAsyncAndDrained(t *testing.T) {
|
||||
observer := &blockingObserver{
|
||||
started: make(chan struct{}),
|
||||
release: make(chan struct{}),
|
||||
}
|
||||
bot := &Bot[NoData]{logger: sneklog.NewLogger(), observer: observer}
|
||||
if err := bot.beginRun(); err != nil {
|
||||
t.Fatalf("beginRun returned error: %v", err)
|
||||
}
|
||||
|
||||
emitted := make(chan struct{})
|
||||
go func() {
|
||||
bot.safeEmitEvent(context.Background(), ErrorEvent{Err: errors.New("boom")})
|
||||
close(emitted)
|
||||
}()
|
||||
select {
|
||||
case <-emitted:
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("safeEmitEvent blocked on observer callback")
|
||||
}
|
||||
select {
|
||||
case <-observer.started:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("observer callback did not start")
|
||||
}
|
||||
|
||||
finished := make(chan struct{})
|
||||
go func() {
|
||||
bot.finishRun()
|
||||
close(finished)
|
||||
}()
|
||||
select {
|
||||
case <-finished:
|
||||
t.Fatal("finishRun returned before the queued callback completed")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
close(observer.release)
|
||||
select {
|
||||
case <-finished:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("finishRun did not drain observer callbacks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWithContextRejectsSecondRun(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
Reference in New Issue
Block a user