mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-11 03:13:46 +00:00
Nine commands mirroring the names @Stickers uses: /newpack, /mypack, /addsticker, /delsticker, /editsticker, /ordersticker, /setpackicon, /renamepack and /delpack, plus a confirm callback for the destructive one. Sources are replied stickers, photos or image documents; photos are downloaded, resampled to 512px and re-uploaded. One pack per user, keyed by owner id. Creating a pack is the only operation here that makes a durable, publicly linkable object on a user's behalf, so it is built around proving ownership rather than assuming it: - A name is claimed globally and create-only before Telegram is called. A pending record alone proves only that a caller *asked* for a name, which is exactly what someone naming a victim's public slug also does. - Adopting an existing set additionally requires that the claim predates this invocation. The claim lives in our store and the pack lives at Telegram, so a wiped store would otherwise make every pack adoptable. - Names are released only on positive evidence that no pack stands behind them, never on a generic failure, so a transient error cannot hand a live name to the next caller. - Ownership refusals are byte-identical across failure modes, so they cannot be used to probe which sets exist. Error classification is positive-only throughout: "the set is gone" and "nothing was created" are each proven from a specific Telegram response, never inferred from an error. Post-action commits run on a context detached from the request so a shutdown mid-handler cannot lose the record of something Telegram already did. Enabled explicitly via MODULES rather than by default.
98 lines
3.0 KiB
Go
98 lines
3.0 KiB
Go
package sticker
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateSlug(t *testing.T) {
|
|
cases := []struct {
|
|
slug string
|
|
ok bool
|
|
}{
|
|
{"mypack", true},
|
|
{"my_pack_2", true},
|
|
{"abc", true},
|
|
{strings.Repeat("a", maxSlugLen), true},
|
|
{"ab", false}, // too short
|
|
{strings.Repeat("a", maxSlugLen+1), false}, // too long
|
|
{"1pack", false}, // leading digit
|
|
{"My_Pack", false}, // uppercase
|
|
{"my__pack", false}, // consecutive underscores
|
|
{"mypack_", false}, // trailing underscore
|
|
{"my-pack", false}, // hyphen
|
|
{"", false},
|
|
}
|
|
for _, tc := range cases {
|
|
err := validateSlug(tc.slug)
|
|
if tc.ok && err != nil {
|
|
t.Errorf("validateSlug(%q) = %v, want nil", tc.slug, err)
|
|
}
|
|
if !tc.ok && err == nil {
|
|
t.Errorf("validateSlug(%q) = nil, want an error", tc.slug)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMakeSetName(t *testing.T) {
|
|
got, err := makeSetName("mypack", "miti99bot")
|
|
if err != nil {
|
|
t.Fatalf("makeSetName: %v", err)
|
|
}
|
|
if want := "mypack_by_miti99bot"; got != want {
|
|
t.Errorf("makeSetName = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
// The set name has a hard 64-char ceiling and the slug is the only part the
|
|
// user controls, so the refusal has to name the budget they actually have.
|
|
func TestMakeSetName_TooLongReportsBudget(t *testing.T) {
|
|
username := strings.Repeat("b", 30)
|
|
slug := strings.Repeat("a", maxSlugLen)
|
|
_, err := makeSetName(slug, username)
|
|
if err == nil {
|
|
t.Fatalf("makeSetName(%d-char slug, %d-char username) succeeded; want a refusal", len(slug), len(username))
|
|
}
|
|
// 64 - len("_by_" + username) = 30
|
|
if !strings.Contains(err.Error(), "30") {
|
|
t.Errorf("refusal %q does not state the remaining budget", err)
|
|
}
|
|
}
|
|
|
|
func TestOwnsSet(t *testing.T) {
|
|
pack := Pack{Slug: "mypack", Name: "mypack_by_bot"}
|
|
cases := []struct {
|
|
setName string
|
|
want bool
|
|
}{
|
|
{"mypack_by_bot", true},
|
|
{"MyPack_By_Bot", true}, // Telegram echoes the creation casing
|
|
{"otherpack_by_bot", false},
|
|
{"mypack_by_otherbot", false},
|
|
{"", false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := ownsSet(pack, tc.setName); got != tc.want {
|
|
t.Errorf("ownsSet(%q) = %v, want %v", tc.setName, got, tc.want)
|
|
}
|
|
}
|
|
if ownsSet(Pack{}, "anything") {
|
|
t.Error("ownsSet with an empty stored name = true, want false")
|
|
}
|
|
}
|
|
|
|
// Renaming the bot in BotFather leaves existing set names untouched. Ownership
|
|
// compares the stored name for exactly this reason: deriving it from the live
|
|
// username would make every user's own pack refuse as "not yours".
|
|
func TestOwnsSet_SurvivesBotRename(t *testing.T) {
|
|
pack := Pack{Slug: "mypack", Name: "mypack_by_oldbot"}
|
|
if !ownsSet(pack, "mypack_by_oldbot") {
|
|
t.Error("pack stopped resolving after the bot was renamed")
|
|
}
|
|
// The new username only ever builds *new* names.
|
|
fresh, err := makeSetName("newpack", "newbot")
|
|
if err != nil || fresh != "newpack_by_newbot" {
|
|
t.Errorf("makeSetName after rename = (%q, %v)", fresh, err)
|
|
}
|
|
}
|