mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-06-08 18:16:54 +00:00
f3b9891a54
Rename: - Go module github.com/tiennm99/miti99bot-go → github.com/tiennm99/miti99bot - CloudFormation stack miti99bot-aws-port → miti99bot - Drop "port", "Cloud Run", "GCP", "cutover", "Phase NN" framing from active code and docs — project reads as canonical AWS-Lambda from now on. AWS deploy guide + flow fix: - New docs/deploy-aws-free-tier-guide.md — Ubuntu 24.04 ARM64 onboarding with project-local venv (pip awscli + sam-cli), SSM secrets via read -s, idempotent OIDC provider + role creation, $1 budget alarm. - Drop sam build from the pipeline — provided.al2023 + makefile builder expects a Makefile in CodeUri (build/lambda/, the output dir), so the step always fails. sam deploy --template-file template.yaml now reads the raw template and zips build/lambda/ directly. - Rollback section rewritten — use continue-update-rollback / cancel-update-stack / git-SHA redeploy. Drop the broken --use-previous-template recipe. - DynamoDB free-tier row corrected (on-demand is 2.5M read / 1M write request units, not 25 RCU/WCU). Updated: - README.md fully rewritten (drops port/legacy framing, lists modules, points new users at the free-tier guide). - aws/README.md retitled "AWS account setup", phase numbers stripped. - Makefile / .github/workflows/deploy.yml — sam deploy flow. - samconfig.toml — stack_name = "miti99bot". - Go comments — Cloud Run → Lambda, Cloud Scheduler → EventBridge Scheduler, Cloud Logging → CloudWatch Logs. - Struct field GCPProject → FirestoreProject (env GOOGLE_CLOUD_PROJECT unchanged). Plus advisory reports under plans/reports/ from the code-reviewer + researcher passes that informed the fixes. Verified: go vet ./..., go build ./..., go test ./... all green.
131 lines
4.5 KiB
Go
131 lines
4.5 KiB
Go
package util_test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-telegram/bot"
|
|
"github.com/go-telegram/bot/models"
|
|
|
|
"github.com/tiennm99/miti99bot/internal/modules"
|
|
"github.com/tiennm99/miti99bot/internal/modules/util"
|
|
"github.com/tiennm99/miti99bot/internal/storage"
|
|
)
|
|
|
|
// helpTestNoop is a stand-in handler used only to satisfy the registry's
|
|
// non-nil-handler validator.
|
|
func helpTestNoop(_ context.Context, _ *bot.Bot, _ *models.Update) error { return nil }
|
|
|
|
// fakeFactory builds a module that exposes the supplied commands. Used to
|
|
// drive RenderHelp without touching the real util/misc factories (avoids a
|
|
// dependency back into the package under test).
|
|
func fakeFactory(name string, cmds []modules.Command) modules.Factory {
|
|
return func(_ modules.Deps) modules.Module {
|
|
return modules.Module{Name: name, Commands: cmds}
|
|
}
|
|
}
|
|
|
|
func TestRenderHelp_GroupsByModuleAndSkipsPrivate(t *testing.T) {
|
|
cmd := func(name string, vis modules.Visibility, desc string) modules.Command {
|
|
return modules.Command{Name: name, Visibility: vis, Description: desc, Handler: helpTestNoop}
|
|
}
|
|
factories := map[string]modules.Factory{
|
|
"alpha": fakeFactory("alpha", []modules.Command{
|
|
cmd("a_pub", modules.VisibilityPublic, "alpha public"),
|
|
cmd("a_prot", modules.VisibilityProtected, "alpha protected"),
|
|
cmd("a_priv", modules.VisibilityPrivate, "alpha private — must not appear"),
|
|
}),
|
|
"beta": fakeFactory("beta", []modules.Command{
|
|
cmd("b_pub", modules.VisibilityPublic, "beta <i>desc</i>"),
|
|
cmd("b_amp", modules.VisibilityPublic, `Tom & "Jerry"`),
|
|
}),
|
|
}
|
|
reg, err := modules.Build([]string{"alpha", "beta"}, factories, storage.NewMemoryProvider(), modules.BuildOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Build: %v", err)
|
|
}
|
|
|
|
out := util.RenderHelp(reg)
|
|
|
|
for _, want := range []string{
|
|
"<b>alpha</b>",
|
|
"<b>beta</b>",
|
|
"/a_pub — alpha public",
|
|
"/a_prot — alpha protected (protected)",
|
|
// HTML in user descriptions must be escaped.
|
|
"beta <i>desc</i>",
|
|
// Locks html.EscapeString contract: & → &, " → ".
|
|
"Tom & "Jerry"",
|
|
// Support footer always present.
|
|
"github.com/tiennm99/miti99bot",
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("output missing %q\n---output---\n%s", want, out)
|
|
}
|
|
}
|
|
if strings.Contains(out, "a_priv") {
|
|
t.Errorf("output leaked private command\n---output---\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestRenderHelp_ModuleOrderMatchesEnvOrder(t *testing.T) {
|
|
cmd := func(name string) modules.Command {
|
|
return modules.Command{Name: name, Visibility: modules.VisibilityPublic, Description: name, Handler: helpTestNoop}
|
|
}
|
|
factories := map[string]modules.Factory{
|
|
"first": fakeFactory("first", []modules.Command{cmd("f1")}),
|
|
"second": fakeFactory("second", []modules.Command{cmd("s1")}),
|
|
}
|
|
|
|
// MODULES order: second,first → expect "second" section before "first".
|
|
reg, err := modules.Build([]string{"second", "first"}, factories, storage.NewMemoryProvider(), modules.BuildOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Build: %v", err)
|
|
}
|
|
out := util.RenderHelp(reg)
|
|
iSecond := strings.Index(out, "<b>second</b>")
|
|
iFirst := strings.Index(out, "<b>first</b>")
|
|
if iSecond < 0 || iFirst < 0 {
|
|
t.Fatalf("missing sections; output:\n%s", out)
|
|
}
|
|
if iSecond >= iFirst {
|
|
t.Errorf("expected 'second' before 'first'; got second=%d first=%d\n%s", iSecond, iFirst, out)
|
|
}
|
|
}
|
|
|
|
func TestRenderHelp_OmitsModulesWithNoVisibleCommands(t *testing.T) {
|
|
cmd := func(name string, vis modules.Visibility) modules.Command {
|
|
return modules.Command{Name: name, Visibility: vis, Description: name, Handler: helpTestNoop}
|
|
}
|
|
factories := map[string]modules.Factory{
|
|
"shadow": fakeFactory("shadow", []modules.Command{
|
|
cmd("hidden", modules.VisibilityPrivate),
|
|
}),
|
|
"visible": fakeFactory("visible", []modules.Command{
|
|
cmd("seen", modules.VisibilityPublic),
|
|
}),
|
|
}
|
|
reg, err := modules.Build([]string{"shadow", "visible"}, factories, storage.NewMemoryProvider(), modules.BuildOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Build: %v", err)
|
|
}
|
|
out := util.RenderHelp(reg)
|
|
if strings.Contains(out, "<b>shadow</b>") {
|
|
t.Errorf("module with only private commands should not render a section\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "<b>visible</b>") {
|
|
t.Errorf("visible module section missing\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestRenderHelp_NilRegistryReturnsFooterOnly(t *testing.T) {
|
|
out := util.RenderHelp(nil)
|
|
if !strings.Contains(out, "no commands registered") {
|
|
t.Errorf("nil registry should render placeholder; got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "github.com/tiennm99/miti99bot") {
|
|
t.Errorf("footer missing; got:\n%s", out)
|
|
}
|
|
}
|