mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-06-08 20:14:23 +00:00
b3dea5fe21
Phase 5 of the 2026-05-09 review remediation plan. Closes the handler-layer test gap (5 modules at 0% coverage in the audit) and ends the storage-package's CI t.Skip on Firestore emulator tests. - internal/testutil: Update fixture builders (NewPrivateMessage, NewGroupMessage, NewSupergroupMessage, NewChannelMessage) plus a RecordingBot that wraps the real go-telegram/bot.Bot with an httptest server. The bot library hits the test server instead of Telegram; multipart form fields are captured per call. Tests assert on Sent() / LastSent() / AssertSentText(). - Handler tests added: misc (4), util (7), wordle (10), loldle (9), loldleemoji (8). Cover happy paths, error paths, auth gates, group-vs-private subject keying, KV side effects. - Coverage 44.7% -> 69.8% (verified via -coverprofile). All packages now report coverage in CI output. - CI: ci.yml installs cloud-firestore-emulator beta component and starts it on localhost:8090 before go test. Sets FIRESTORE_EMULATOR_HOST + GOOGLE_CLOUD_PROJECT env so the storage package's emulator-gated tests execute instead of skipping. go test -race -count=1 ./... clean across all 15 packages locally.
92 lines
2.9 KiB
Go
92 lines
2.9 KiB
Go
// Package testutil provides shared fixtures for handler-layer integration
|
|
// tests: update builders, a recording Bot that captures outbound API calls
|
|
// instead of hitting Telegram, and helpers to assert on captured calls.
|
|
//
|
|
// Tests should construct a real *bot.Bot (via NewRecordingBot), register
|
|
// real module handlers against it, dispatch a fixture *models.Update via
|
|
// Bot.ProcessUpdate, and then inspect Sent() to verify the reply.
|
|
package testutil
|
|
|
|
import (
|
|
"github.com/go-telegram/bot/models"
|
|
)
|
|
|
|
// NewPrivateMessage builds an Update for a 1:1 private DM. userID is both
|
|
// the chat id and the From id (private chats use the user as the chat id).
|
|
func NewPrivateMessage(userID int64, text string) *models.Update {
|
|
return &models.Update{
|
|
ID: 1,
|
|
Message: &models.Message{
|
|
ID: 1,
|
|
Text: text,
|
|
Chat: models.Chat{ID: userID, Type: models.ChatTypePrivate},
|
|
From: &models.User{ID: userID, FirstName: "Test"},
|
|
Entities: []models.MessageEntity{
|
|
botCommandEntity(text),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewGroupMessage builds an Update for a group chat where chatID and userID
|
|
// are distinct (group rules: subject = chat ID, sender = user ID).
|
|
func NewGroupMessage(chatID, userID int64, text string) *models.Update {
|
|
return &models.Update{
|
|
ID: 1,
|
|
Message: &models.Message{
|
|
ID: 1,
|
|
Text: text,
|
|
Chat: models.Chat{ID: chatID, Type: models.ChatTypeGroup, Title: "Test Group"},
|
|
From: &models.User{ID: userID, FirstName: "Test"},
|
|
Entities: []models.MessageEntity{
|
|
botCommandEntity(text),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewSupergroupMessage is the same shape as NewGroupMessage but with
|
|
// supergroup chat type — exercises the second branch in chathelper.SubjectFor.
|
|
func NewSupergroupMessage(chatID, userID int64, text string) *models.Update {
|
|
u := NewGroupMessage(chatID, userID, text)
|
|
u.Message.Chat.Type = models.ChatTypeSupergroup
|
|
return u
|
|
}
|
|
|
|
// NewChannelMessage builds an Update for a channel post — no From field on
|
|
// most channel posts. Used to exercise the "no usable subject id" path.
|
|
func NewChannelMessage(chatID int64, text string) *models.Update {
|
|
return &models.Update{
|
|
ID: 1,
|
|
Message: &models.Message{
|
|
ID: 1,
|
|
Text: text,
|
|
Chat: models.Chat{ID: chatID, Type: models.ChatTypeChannel, Title: "Test Channel"},
|
|
Entities: []models.MessageEntity{
|
|
botCommandEntity(text),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// botCommandEntity is what Telegram attaches when a message starts with `/`.
|
|
// The dispatcher's MatchTypeCommand uses it to extract the command name, so
|
|
// every fixture command-bearing message must include one.
|
|
func botCommandEntity(text string) models.MessageEntity {
|
|
if len(text) == 0 || text[0] != '/' {
|
|
return models.MessageEntity{Type: models.MessageEntityTypeBotCommand}
|
|
}
|
|
end := len(text)
|
|
for i := 1; i < len(text); i++ {
|
|
if text[i] == ' ' || text[i] == '@' {
|
|
end = i
|
|
break
|
|
}
|
|
}
|
|
return models.MessageEntity{
|
|
Type: models.MessageEntityTypeBotCommand,
|
|
Offset: 0,
|
|
Length: end,
|
|
}
|
|
}
|