(new): expand runtime APIs
Golang lint / lint (push) Successful in 58s
Golang lint / lint (pull_request) Successful in 13m10s

(fix): harden concurrent lifecycle
(tests): add regression coverage
(doc): update v1.2 guidance
This commit is contained in:
2026-08-20 11:08:45 +03:00
parent 29b208eeec
commit 24040fe164
37 changed files with 1129 additions and 157 deletions
+68
View File
@@ -22,6 +22,14 @@ func (f pollingRoundTripFunc) RoundTrip(req *http.Request) (*http.Response, erro
return f(req)
}
type closeCountingWriter struct{ closes int }
func (w *closeCountingWriter) Close() error { w.closes++; return nil }
func (w *closeCountingWriter) Write(p []byte) (int, error) { return len(p), nil }
func (w *closeCountingWriter) Print(sneklog.LogLevel, string, []*sneklog.MethodTraceback, ...any) error {
return nil
}
type pollingRetryObserver struct {
recordingObserver
cancel context.CancelFunc
@@ -343,6 +351,66 @@ func TestLoggerConfigurationRejectsNilAndLateMutation(t *testing.T) {
}
}
func TestCloseRespectsLoggerOwnershipAndAliases(t *testing.T) {
ownedWriter := new(closeCountingWriter)
owned := sneklog.NewLogger().AddWriter(ownedWriter)
bot := &Bot[NoData]{
logger: owned, loggerOwned: true,
requestLogger: owned, requestLoggerOwned: false,
}
if err := bot.Close(); err != nil {
t.Fatalf("Close returned error: %v", err)
}
if ownedWriter.closes != 1 {
t.Fatalf("owned aliased logger closed %d times, want 1", ownedWriter.closes)
}
callerWriter := new(closeCountingWriter)
callerLogger := sneklog.NewLogger().AddWriter(callerWriter)
bot = &Bot[NoData]{logger: callerLogger, requestLogger: callerLogger, webhookLogger: callerLogger}
if err := bot.Close(); err != nil {
t.Fatalf("Close with caller logger returned error: %v", err)
}
if callerWriter.closes != 0 {
t.Fatalf("caller-owned logger closed %d times", callerWriter.closes)
}
_ = callerLogger.Close()
}
func TestRunWithContextReturnsPollingPanic(t *testing.T) {
bot := &Bot[NoData]{
logger: sneklog.NewLogger(), prefixes: []string{"/"},
plugins: []Plugin[NoData]{{name: "demo"}}, updateQueue: make(chan *tgapi.Update, 1), maxWorkers: 1,
}
defer func() { _ = bot.logger.Close() }()
err := bot.RunWithContext(context.Background())
if !errors.Is(err, ErrHandlerPanic) {
t.Fatalf("RunWithContext error = %v, want ErrHandlerPanic", err)
}
}
func TestNewBotWithAPIUsesInjectedClient(t *testing.T) {
client := &http.Client{Transport: pollingRoundTripFunc(func(*http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/json"}},
Body: io.NopCloser(strings.NewReader(`{"ok":true,"result":{"id":7,"is_bot":true,"first_name":"Test","username":"test_bot"}}`)),
}, nil
})}
api := tgapi.NewAPI(tgapi.NewAPIOpts("token").SetAPIURL("http://example.invalid").SetHTTPClient(client))
bot, err := NewBotWithAPI[NoData](&BotOpts{Token: "token"}, api)
if err != nil {
t.Fatalf("NewBotWithAPI returned error: %v", err)
}
if bot.GetAPI() != api || bot.GetUploader() == nil {
t.Fatal("injected API was not used consistently")
}
if err := bot.Close(); err != nil {
t.Fatalf("Close returned error: %v", err)
}
}
func TestAddAppDataLoggerWriterRejectsNilResult(t *testing.T) {
bot := &Bot[NoData]{logger: sneklog.NewLogger(), hasAppData: true}
defer func() { _ = bot.logger.Close() }()