221 lines
9.7 KiB
Go
221 lines
9.7 KiB
Go
package help
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"testing"
|
||
|
||
"telesrv/internal/domain"
|
||
)
|
||
|
||
// TestAppConfigPremiumKeys 断言 premium / Stars 相关 key 完整下发且 hash 已递增:
|
||
// premium_purchase_blocked 必须显式为 false——客户端把 star gift「Send a Gift」入口与
|
||
// premiumCanBuy()=!premium_purchase_blocked 耦合,置 true 会同时隐藏送礼入口;
|
||
// reactions_user_max_premium 必须与服务端 enforcement 档位一致。
|
||
func TestAppConfigPremiumKeys(t *testing.T) {
|
||
cfg, notModified, err := (*Service)(nil).GetAppConfig(context.Background(), 0, 0)
|
||
if err != nil || notModified {
|
||
t.Fatalf("GetAppConfig = notModified %v err %v", notModified, err)
|
||
}
|
||
if cfg.Hash != defaultAppConfigHash || cfg.Hash < 10 {
|
||
t.Fatalf("hash = %d, want defaultAppConfigHash(≥10)", cfg.Hash)
|
||
}
|
||
oldCfg, oldNotModified, err := (*Service)(nil).GetAppConfig(context.Background(), 0, defaultAppConfigHash-1)
|
||
if err != nil || oldNotModified || oldCfg.Hash != defaultAppConfigHash {
|
||
t.Fatalf("GetAppConfig(old hash) = hash %d notModified %v err %v, want refreshed config", oldCfg.Hash, oldNotModified, err)
|
||
}
|
||
var decoded map[string]any
|
||
if err := json.Unmarshal(cfg.JSON, &decoded); err != nil {
|
||
t.Fatalf("app config json invalid: %v", err)
|
||
}
|
||
if period, ok := decoded["no_forwards_request_expire_period"].(float64); !ok || int(period) != domain.PrivateNoForwardsRequestExpirePeriod {
|
||
t.Fatalf("no_forwards_request_expire_period = %v, want %d", decoded["no_forwards_request_expire_period"], domain.PrivateNoForwardsRequestExpirePeriod)
|
||
}
|
||
if blocked, ok := decoded["premium_purchase_blocked"].(bool); !ok || blocked {
|
||
t.Fatalf("premium_purchase_blocked = %v, want false (star gift 送礼入口耦合此 flag)", decoded["premium_purchase_blocked"])
|
||
}
|
||
// DrKLO 缺省 starsLocked=true;缺 key 时余额不足送礼会误弹「所在国家无法购买星星」。
|
||
if blocked, ok := decoded["stars_purchase_blocked"].(bool); !ok || blocked {
|
||
t.Fatalf("stars_purchase_blocked = %v, want false (DrKLO starsPurchaseAvailable 据此解锁充值入口)", decoded["stars_purchase_blocked"])
|
||
}
|
||
// DrKLO 缺省 stargiftsBlocked=true 会隐藏 star gift 送礼网格,必须显式下发 false。
|
||
if blocked, ok := decoded["stargifts_blocked"].(bool); !ok || blocked {
|
||
t.Fatalf("stargifts_blocked = %v, want false (DrKLO GiftSheet 据此隐藏礼物网格)", decoded["stargifts_blocked"])
|
||
}
|
||
if available, ok := decoded["giveaway_gifts_purchase_available"].(bool); !ok || !available {
|
||
t.Fatalf("giveaway_gifts_purchase_available = %v, want true", decoded["giveaway_gifts_purchase_available"])
|
||
}
|
||
directCurrencies, ok := decoded["premium_playmarket_direct_currency_list"].([]any)
|
||
if !ok || len(directCurrencies) == 0 || !containsJSONCurrency(directCurrencies, "USD") {
|
||
t.Fatalf("premium_playmarket_direct_currency_list = %#v, want non-empty list containing USD", decoded["premium_playmarket_direct_currency_list"])
|
||
}
|
||
if posting, ok := decoded["rich_message_posting"].(string); !ok || posting != "enabled" {
|
||
t.Fatalf("rich_message_posting = %v, want enabled (TDesktop 富文本编辑入口默认打开)", decoded["rich_message_posting"])
|
||
}
|
||
fragmentPrefixes, ok := decoded["fragment_prefixes"].([]any)
|
||
if !ok || len(fragmentPrefixes) != 1 || fragmentPrefixes[0] != "888" {
|
||
t.Fatalf("fragment_prefixes = %#v, want [\"888\"]", decoded["fragment_prefixes"])
|
||
}
|
||
wantNumbers := map[string]float64{
|
||
"giveaway_boosts_per_premium": 4,
|
||
"giveaway_countries_max": 10,
|
||
"giveaway_add_peers_max": 10,
|
||
"giveaway_period_max": 604800,
|
||
"reactions_user_max_default": 1,
|
||
"reactions_user_max_premium": 3,
|
||
"boosts_channel_level_max": 100,
|
||
"stargifts_pinned_to_top_limit": 6,
|
||
"about_length_limit_default": 70,
|
||
"about_length_limit_premium": 140,
|
||
"bot_verification_description_length_limit": 70,
|
||
"dialogs_pinned_limit_default": 5,
|
||
"dialogs_pinned_limit_premium": 10,
|
||
"dialogs_folder_pinned_limit_default": 100,
|
||
"dialogs_folder_pinned_limit_premium": 200,
|
||
"saved_dialogs_pinned_limit_default": 5,
|
||
"saved_dialogs_pinned_limit_premium": 100,
|
||
"caption_length_limit_default": 1024,
|
||
"caption_length_limit_premium": 4096,
|
||
"channels_limit_default": 500,
|
||
"channels_limit_premium": 1000,
|
||
"dialog_filters_limit_default": 10,
|
||
"dialog_filters_limit_premium": 20,
|
||
"chatlist_update_period": 3600,
|
||
"chatlist_invites_limit_default": 3,
|
||
"chatlist_invites_limit_premium": 20,
|
||
"chatlists_joined_limit_default": 2,
|
||
"chatlists_joined_limit_premium": 20,
|
||
"upload_max_fileparts_default": 4000,
|
||
"upload_max_fileparts_premium": 8000,
|
||
"aicompose_tone_examples_num": 3,
|
||
"aicompose_tone_title_length_max": 12,
|
||
"aicompose_tone_prompt_length_max": 1024,
|
||
"aicompose_tone_saved_limit_default": 5,
|
||
"aicompose_tone_saved_limit_premium": 20,
|
||
"stories_stealth_future_period": 1500,
|
||
"stories_stealth_past_period": 300,
|
||
"stories_stealth_cooldown_period": 10800,
|
||
}
|
||
for key, want := range wantNumbers {
|
||
got, ok := decoded[key].(float64)
|
||
if !ok || got != want {
|
||
t.Errorf("appConfig[%q] = %v, want %v", key, decoded[key], want)
|
||
}
|
||
}
|
||
// 未实现功能族的 key 不得下发(诱导客户端进入未实现路径)。
|
||
for _, forbidden := range []string{"stories_sent_weekly_limit_default", "premium_bot_username", "premium_invoice_slug"} {
|
||
if _, ok := decoded[forbidden]; ok {
|
||
t.Errorf("appConfig 不应包含 %q", forbidden)
|
||
}
|
||
}
|
||
}
|
||
|
||
func containsJSONCurrency(values []any, want string) bool {
|
||
for _, value := range values {
|
||
if value == want {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func TestAppConfigKeepsStockUploadPartsLimitsByDefault(t *testing.T) {
|
||
cfg, notModified, err := (*Service)(nil).GetAppConfig(context.Background(), 0, 0)
|
||
if err != nil || notModified {
|
||
t.Fatalf("GetAppConfig = notModified %v err %v", notModified, err)
|
||
}
|
||
var decoded map[string]any
|
||
if err := json.Unmarshal(cfg.JSON, &decoded); err != nil {
|
||
t.Fatalf("app config json invalid: %v", err)
|
||
}
|
||
if got := decoded["upload_max_fileparts_default"]; got != float64(4000) {
|
||
t.Fatalf("upload_max_fileparts_default = %v, want 4000 (unconfigured limit)", got)
|
||
}
|
||
if got := decoded["upload_max_fileparts_premium"]; got != float64(8000) {
|
||
t.Fatalf("upload_max_fileparts_premium = %v, want 8000 (unconfigured limit)", got)
|
||
}
|
||
}
|
||
|
||
func TestAppConfigUsesConfiguredMaxUploadFileBytesAndHash(t *testing.T) {
|
||
// 500MiB / 512KiB-per-part = 1000 parts exactly.
|
||
svc := NewService(nil, nil, WithMaxUploadFileBytes(500*1024*1024))
|
||
cfg, notModified, err := svc.GetAppConfig(context.Background(), 0, 0)
|
||
if err != nil || notModified {
|
||
t.Fatalf("GetAppConfig = notModified %v err %v", notModified, err)
|
||
}
|
||
if cfg.Hash == defaultAppConfigHash {
|
||
t.Fatalf("hash = %d, want limit-specific hash", cfg.Hash)
|
||
}
|
||
if _, notModified, err := svc.GetAppConfig(context.Background(), 0, cfg.Hash); err != nil || !notModified {
|
||
t.Fatalf("GetAppConfig(hash) = notModified %v err %v, want notModified", notModified, err)
|
||
}
|
||
var decoded map[string]any
|
||
if err := json.Unmarshal(cfg.JSON, &decoded); err != nil {
|
||
t.Fatalf("app config json invalid: %v", err)
|
||
}
|
||
if got := decoded["upload_max_fileparts_default"]; got != float64(1000) {
|
||
t.Fatalf("upload_max_fileparts_default = %v, want 1000", got)
|
||
}
|
||
if got := decoded["upload_max_fileparts_premium"]; got != float64(1000) {
|
||
t.Fatalf("upload_max_fileparts_premium = %v, want 1000", got)
|
||
}
|
||
}
|
||
|
||
func TestMaxUploadFilePartsRoundsUpAndFloorsAtOne(t *testing.T) {
|
||
cases := []struct {
|
||
bytes int64
|
||
want int64
|
||
}{
|
||
{0, 1},
|
||
{1, 1},
|
||
{maxUploadFilePartBytes, 1},
|
||
{maxUploadFilePartBytes + 1, 2},
|
||
{500 * 1024 * 1024, 1000},
|
||
}
|
||
for _, c := range cases {
|
||
if got := maxUploadFileParts(c.bytes); got != c.want {
|
||
t.Errorf("maxUploadFileParts(%d) = %d, want %d", c.bytes, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestAppConfigOmitsMapboxTokenByDefault(t *testing.T) {
|
||
cfg, notModified, err := (*Service)(nil).GetAppConfig(context.Background(), 0, 0)
|
||
if err != nil || notModified {
|
||
t.Fatalf("GetAppConfig = notModified %v err %v", notModified, err)
|
||
}
|
||
var decoded map[string]any
|
||
if err := json.Unmarshal(cfg.JSON, &decoded); err != nil {
|
||
t.Fatalf("app config json invalid: %v", err)
|
||
}
|
||
if _, ok := decoded["tdesktop_config_map"]; ok {
|
||
t.Fatal("tdesktop_config_map present without configured Mapbox token")
|
||
}
|
||
}
|
||
|
||
func TestAppConfigUsesConfiguredMapboxTokenAndHash(t *testing.T) {
|
||
svc := NewService(nil, nil, WithMapboxToken("pk.test-token"))
|
||
cfg, notModified, err := svc.GetAppConfig(context.Background(), 0, 0)
|
||
if err != nil || notModified {
|
||
t.Fatalf("GetAppConfig = notModified %v err %v", notModified, err)
|
||
}
|
||
if cfg.Hash == defaultAppConfigHash {
|
||
t.Fatalf("hash = %d, want token-specific hash", cfg.Hash)
|
||
}
|
||
if _, notModified, err := svc.GetAppConfig(context.Background(), 0, cfg.Hash); err != nil || !notModified {
|
||
t.Fatalf("GetAppConfig(hash) = notModified %v err %v, want notModified", notModified, err)
|
||
}
|
||
var decoded map[string]any
|
||
if err := json.Unmarshal(cfg.JSON, &decoded); err != nil {
|
||
t.Fatalf("app config json invalid: %v", err)
|
||
}
|
||
configMap, ok := decoded["tdesktop_config_map"].(map[string]any)
|
||
if !ok {
|
||
t.Fatalf("tdesktop_config_map = %T, want object", decoded["tdesktop_config_map"])
|
||
}
|
||
for _, key := range []string{"maps", "geo", "bmaps", "bgeo"} {
|
||
if got := configMap[key]; got != "pk.test-token" {
|
||
t.Fatalf("tdesktop_config_map[%q] = %v, want token", key, got)
|
||
}
|
||
}
|
||
}
|