feat: sync langpack and auth updates
Sync telesrv commits 49f9bab and 04d9563 into the public mirror. Exclude private docs and runtime key material per sync rules.
This commit is contained in:
parent
6af61f26ba
commit
1292540350
244 changed files with 1552172 additions and 27668 deletions
|
|
@ -47,6 +47,57 @@ func TestEmailSentCodeUsesDeliveryLength(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAuthSignInRoutesOfficialEmailCodeCarriers(t *testing.T) {
|
||||
const (
|
||||
phone = "+86 188 0000 0021"
|
||||
hash = "hash-email-login"
|
||||
code = "654321"
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
request func() *tg.AuthSignInRequest
|
||||
wantPhoneCodeCalls int
|
||||
wantEmailCodeCalls int
|
||||
}{
|
||||
{
|
||||
name: "webk_phone_code",
|
||||
request: func() *tg.AuthSignInRequest {
|
||||
return &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash, PhoneCode: code}
|
||||
},
|
||||
wantPhoneCodeCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "tdesktop_android_email_verification",
|
||||
request: func() *tg.AuthSignInRequest {
|
||||
req := &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash}
|
||||
req.SetEmailVerification(&tg.EmailVerificationCode{Code: code})
|
||||
return req
|
||||
},
|
||||
wantEmailCodeCalls: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
authSvc := &captureAuthService{signInUser: domain.User{ID: 100200301, Phone: "8618800000021", FirstName: "Alice"}}
|
||||
r := New(Config{}, Deps{Auth: authSvc}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000000, 0)})
|
||||
|
||||
if _, err := r.onAuthSignIn(context.Background(), tc.request()); err != nil {
|
||||
t.Fatalf("onAuthSignIn: %v", err)
|
||||
}
|
||||
if authSvc.signInCount != tc.wantPhoneCodeCalls || authSvc.signInWithEmailCount != tc.wantEmailCodeCalls {
|
||||
t.Fatalf("SignIn/SignInWithEmail calls=%d/%d, want %d/%d", authSvc.signInCount, authSvc.signInWithEmailCount, tc.wantPhoneCodeCalls, tc.wantEmailCodeCalls)
|
||||
}
|
||||
if tc.wantPhoneCodeCalls == 1 && (authSvc.signInPhone != phone || authSvc.signInHash != hash || authSvc.signInCode != code) {
|
||||
t.Fatalf("SignIn proof=%q/%q/%q, want %q/%q/%q", authSvc.signInPhone, authSvc.signInHash, authSvc.signInCode, phone, hash, code)
|
||||
}
|
||||
if tc.wantEmailCodeCalls == 1 && (authSvc.signInWithEmailPhone != phone || authSvc.signInWithEmailHash != hash || authSvc.signInWithEmailCode != code) {
|
||||
t.Fatalf("SignInWithEmail proof=%q/%q/%q, want %q/%q/%q", authSvc.signInWithEmailPhone, authSvc.signInWithEmailHash, authSvc.signInWithEmailCode, phone, hash, code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountVerifyEmailLoginSetupReturnsSentCodeSuccess(t *testing.T) {
|
||||
user := domain.User{
|
||||
ID: 100200300,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,32 @@ package rpc
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"telesrv/internal/domain"
|
||||
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
"sort"
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
func tgLangPackLanguages(items []domain.LangPackLanguage) []tg.LangPackLanguage {
|
||||
out := make([]tg.LangPackLanguage, 0, len(items))
|
||||
for _, item := range items {
|
||||
out = append(out, tg.LangPackLanguage{
|
||||
Official: item.Official,
|
||||
Rtl: item.Rtl,
|
||||
Beta: item.Beta,
|
||||
Name: item.Name,
|
||||
NativeName: item.NativeName,
|
||||
LangCode: item.LangCode,
|
||||
BaseLangCode: item.BaseLangCode,
|
||||
PluralCode: item.PluralCode,
|
||||
StringsCount: item.StringsCount,
|
||||
TranslatedCount: item.TranslatedCount,
|
||||
TranslationsURL: item.TranslationsURL,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func tgLangPackStrings(items []domain.LangPackString) []tg.LangPackStringClass {
|
||||
out := make([]tg.LangPackStringClass, 0, len(items))
|
||||
for _, item := range items {
|
||||
|
|
|
|||
|
|
@ -775,6 +775,7 @@ type LangPackService interface {
|
|||
GetLangPack(ctx context.Context, langPack, langCode string) (domain.LangPack, error)
|
||||
GetDifference(ctx context.Context, langPack, langCode string, fromVersion int) (domain.LangPack, error)
|
||||
GetStrings(ctx context.Context, langPack, langCode string, keys []string) (domain.LangPack, error)
|
||||
ListLanguages(ctx context.Context, langPack string) ([]domain.LangPackLanguage, error)
|
||||
}
|
||||
|
||||
// AIComposeService 抽象客户端输入框 AI 改写/润色与 aicompose tones 目录。
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ func wrapperTooDeepErr() error { return tgerr.New(400, "WRAPPER_TOO_DEEP") }
|
|||
// inputConstructorInvalidErr 表示客户端传入的 TL 构造器不在当前 RPC 接受范围内。
|
||||
func inputConstructorInvalidErr() error { return tgerr.New(400, "INPUT_CONSTRUCTOR_INVALID") }
|
||||
|
||||
// langCodeNotSupportedErr 表示请求的语言码没有已导入的语言包。
|
||||
func langCodeNotSupportedErr() error { return tgerr.New(400, "LANG_CODE_NOT_SUPPORTED") }
|
||||
|
||||
// folderIDInvalidErr 表示客户端传入多个 folder peer 或非法 folder。
|
||||
func folderIDInvalidErr() error { return tgerr.New(400, "FOLDER_ID_INVALID") }
|
||||
|
||||
|
|
|
|||
|
|
@ -17,15 +17,21 @@ func (r *Router) registerLangpack(d *tlprofile.Dispatcher) {
|
|||
registerRPC[*tg.LangpackGetLanguagesRequest](d, tlprofile.SemanticMethodLangpackGetLanguages, func(ctx context.Context, layerRequest *tg.LangpackGetLanguagesRequest) (any, error) {
|
||||
langPack := layerRequest.
|
||||
LangPack
|
||||
_ = langPack
|
||||
|
||||
return r.langpackLanguages(ctx, langPack), nil
|
||||
languages, err := r.langpackLanguages(ctx, langPack)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
return languages, nil
|
||||
})
|
||||
registerRPC[*tg.LangpackGetLanguageRequest](d, tlprofile.SemanticMethodLangpackGetLanguage, func(ctx context.Context, req *tg.LangpackGetLanguageRequest) (any, error) {
|
||||
if req == nil {
|
||||
return nil, inputConstructorInvalidErr()
|
||||
}
|
||||
lang := r.langpackLanguage(ctx, req.LangPack, req.LangCode)
|
||||
lang, err := r.langpackLanguage(ctx, req.LangPack, req.LangCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &lang, nil
|
||||
})
|
||||
registerRPC[*tg.LangpackGetLangPackRequest](d, tlprofile.SemanticMethodLangpackGetLangPack, func(ctx context.Context, req *tg.LangpackGetLangPackRequest) (any, error) {
|
||||
|
|
@ -71,7 +77,7 @@ func langPackOrClient(ctx context.Context, langPack string) string {
|
|||
return langPackFromClient(ctx)
|
||||
}
|
||||
|
||||
func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string) tg.LangPackLanguage {
|
||||
func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string) (tg.LangPackLanguage, error) {
|
||||
if langCode == "" {
|
||||
if info, ok := ClientInfoFrom(ctx); ok && info.LangCode != "" {
|
||||
langCode = info.LangCode
|
||||
|
|
@ -80,61 +86,31 @@ func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string
|
|||
}
|
||||
}
|
||||
langCode = normalizeLangpackCode(langCode)
|
||||
languages := r.langpackLanguages(ctx, langPack)
|
||||
languages, err := r.langpackLanguages(ctx, langPack)
|
||||
if err != nil {
|
||||
return tg.LangPackLanguage{}, internalErr()
|
||||
}
|
||||
for _, lang := range languages {
|
||||
if strings.ToLower(lang.LangCode) == langCode {
|
||||
return lang
|
||||
return lang, nil
|
||||
}
|
||||
}
|
||||
for _, lang := range languages {
|
||||
if strings.ToLower(lang.PluralCode) == langCode {
|
||||
return lang
|
||||
}
|
||||
}
|
||||
return languages[0]
|
||||
return tg.LangPackLanguage{}, langCodeNotSupportedErr()
|
||||
}
|
||||
|
||||
func (r *Router) langpackLanguages(ctx context.Context, langPack string) []tg.LangPackLanguage {
|
||||
func (r *Router) langpackLanguages(ctx context.Context, langPack string) ([]tg.LangPackLanguage, error) {
|
||||
if langPack == "" {
|
||||
langPack = langPackFromClient(ctx)
|
||||
}
|
||||
langPack = strings.ToLower(langPack)
|
||||
languages := []tg.LangPackLanguage{
|
||||
{
|
||||
Official: true,
|
||||
Name: "English",
|
||||
NativeName: "English",
|
||||
LangCode: "en",
|
||||
PluralCode: "en",
|
||||
StringsCount: 0,
|
||||
TranslatedCount: 0,
|
||||
TranslationsURL: "",
|
||||
},
|
||||
{
|
||||
Official: true,
|
||||
Name: "Chinese (Simplified)",
|
||||
NativeName: "Chinese (Simplified)",
|
||||
LangCode: "zh-hans",
|
||||
PluralCode: "zh",
|
||||
StringsCount: 0,
|
||||
TranslatedCount: 0,
|
||||
TranslationsURL: "",
|
||||
},
|
||||
if r.deps.LangPack == nil {
|
||||
return []tg.LangPackLanguage{}, nil
|
||||
}
|
||||
if langPack == "android" {
|
||||
languages = append(languages, tg.LangPackLanguage{
|
||||
Official: true,
|
||||
Rtl: true,
|
||||
Name: "Persian",
|
||||
NativeName: "فارسی",
|
||||
LangCode: "fa",
|
||||
PluralCode: "fa",
|
||||
StringsCount: 11002,
|
||||
TranslatedCount: 11002,
|
||||
TranslationsURL: "",
|
||||
})
|
||||
languages, err := r.deps.LangPack.ListLanguages(ctx, langPack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return languages
|
||||
return tgLangPackLanguages(languages), nil
|
||||
}
|
||||
|
||||
func langPackFromClient(ctx context.Context) string {
|
||||
|
|
@ -171,5 +147,6 @@ func normalizeLangpackCode(langCode string) string {
|
|||
if code == "" {
|
||||
return "en"
|
||||
}
|
||||
code = strings.ReplaceAll(code, "_", "-")
|
||||
return strings.TrimSuffix(code, "-raw")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package rpc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
|
@ -9,10 +10,14 @@ import (
|
|||
"github.com/iamxvbaba/td/bin"
|
||||
"github.com/iamxvbaba/td/clock"
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
|
||||
applangpack "telesrv/internal/app/langpack"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
func TestLangpackGetLanguagesCurrentAndLegacy(t *testing.T) {
|
||||
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
||||
r := newSeededLangpackRouter(t)
|
||||
|
||||
t.Run("current layer", func(t *testing.T) {
|
||||
var in bin.Buffer
|
||||
|
|
@ -47,7 +52,7 @@ func TestLangpackGetLanguagesCurrentAndLegacy(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLangpackGetLanguage(t *testing.T) {
|
||||
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
||||
r := newSeededLangpackRouter(t)
|
||||
|
||||
var in bin.Buffer
|
||||
if err := (&tg.LangpackGetLanguageRequest{LangPack: "tdesktop", LangCode: "zh-hans"}).Encode(&in); err != nil {
|
||||
|
|
@ -69,21 +74,33 @@ func TestLangpackGetLanguage(t *testing.T) {
|
|||
t.Fatalf("language = %+v, want zh-hans", lang)
|
||||
}
|
||||
|
||||
raw := r.langpackLanguage(context.Background(), "tdesktop", "en-raw")
|
||||
raw, err := r.langpackLanguage(context.Background(), "tdesktop", "en-raw")
|
||||
if err != nil {
|
||||
t.Fatalf("language(en-raw): %v", err)
|
||||
}
|
||||
if raw.LangCode != "en" {
|
||||
t.Fatalf("language(en-raw) = %+v, want en", raw)
|
||||
}
|
||||
if _, err := r.langpackLanguage(context.Background(), "tdesktop", "zh"); err == nil || !strings.Contains(err.Error(), "LANG_CODE_NOT_SUPPORTED") {
|
||||
t.Fatalf("language(zh) error = %v, want LANG_CODE_NOT_SUPPORTED", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLangpackAndroidPersianLanguage(t *testing.T) {
|
||||
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
|
||||
r := newSeededLangpackRouter(t)
|
||||
|
||||
lang := r.langpackLanguage(context.Background(), "android", "fa")
|
||||
if lang.LangCode != "fa" || lang.PluralCode != "fa" || lang.Name != "Persian" || lang.NativeName != "فارسی" || !lang.Rtl {
|
||||
lang, err := r.langpackLanguage(context.Background(), "android", "fa")
|
||||
if err != nil {
|
||||
t.Fatalf("android fa language: %v", err)
|
||||
}
|
||||
if lang.LangCode != "fa" || lang.PluralCode != "fa" || lang.NativeName != "فارسی" || !lang.Rtl {
|
||||
t.Fatalf("android fa language = %+v", lang)
|
||||
}
|
||||
|
||||
languages := r.langpackLanguages(androidClientContext(), "")
|
||||
languages, err := r.langpackLanguages(androidClientContext(), "")
|
||||
if err != nil {
|
||||
t.Fatalf("android languages: %v", err)
|
||||
}
|
||||
for _, item := range languages {
|
||||
if item.LangCode == "fa" {
|
||||
return
|
||||
|
|
@ -191,6 +208,53 @@ func TestLegacyAccountRegisterDevice(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func newSeededLangpackRouter(t *testing.T) *Router {
|
||||
t.Helper()
|
||||
return New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
|
||||
LangPack: seededLangPackService(t),
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
}
|
||||
|
||||
func seededLangPackService(t testing.TB) LangPackService {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
store := memory.NewLangPackStore()
|
||||
for _, pack := range []domain.LangPack{
|
||||
{
|
||||
LangPack: "tdesktop",
|
||||
LangCode: "en",
|
||||
Version: 1,
|
||||
Strings: []domain.LangPackString{{Key: "lng_language_name", Value: "English"}},
|
||||
},
|
||||
{
|
||||
LangPack: "tdesktop",
|
||||
LangCode: "zh-hans",
|
||||
Version: 1,
|
||||
Strings: []domain.LangPackString{{Key: "lng_language_name", Value: "简体中文"}},
|
||||
},
|
||||
{
|
||||
LangPack: "android",
|
||||
LangCode: "en",
|
||||
Version: 1,
|
||||
Strings: []domain.LangPackString{{Key: "LanguageName", Value: "English"}},
|
||||
},
|
||||
{
|
||||
LangPack: "android",
|
||||
LangCode: "fa",
|
||||
Version: 1,
|
||||
Strings: []domain.LangPackString{
|
||||
{Key: "LanguageName", Value: "انگلیسی"},
|
||||
{Key: "TranslateLanguageFA", Value: "فارسی"},
|
||||
},
|
||||
},
|
||||
} {
|
||||
if err := store.UpsertPack(ctx, pack); err != nil {
|
||||
t.Fatalf("seed %s/%s: %v", pack.LangPack, pack.LangCode, err)
|
||||
}
|
||||
}
|
||||
return applangpack.NewService(store)
|
||||
}
|
||||
|
||||
func dispatchLangpackLanguages(t *testing.T, r *Router, ctx context.Context, in *bin.Buffer) []tg.LangPackLanguage {
|
||||
t.Helper()
|
||||
enc, err := r.Dispatch(ctx, [8]byte{}, 0, in)
|
||||
|
|
|
|||
|
|
@ -1206,6 +1206,7 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
|
|||
LastName: "User",
|
||||
Phone: "15550000000",
|
||||
}},
|
||||
LangPack: seededLangPackService(t),
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
|
||||
tests := []struct {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,14 @@ type captureAuthService struct {
|
|||
completedPasswordKey [8]byte
|
||||
completePasswordCount int
|
||||
codeDelivery domain.AuthCodeDelivery
|
||||
signInCount int
|
||||
signInPhone string
|
||||
signInHash string
|
||||
signInCode string
|
||||
signInWithEmailCount int
|
||||
signInWithEmailPhone string
|
||||
signInWithEmailHash string
|
||||
signInWithEmailCode string
|
||||
}
|
||||
|
||||
type blockingUserAuthService struct {
|
||||
|
|
@ -188,15 +195,22 @@ func (s *captureAuthService) CancelCode(context.Context, string, string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *captureAuthService) SignIn(context.Context, domain.Authorization, string, string, string) (domain.User, domain.Message, bool, error) {
|
||||
func (s *captureAuthService) SignIn(_ context.Context, _ domain.Authorization, phone, hash, code string) (domain.User, domain.Message, bool, error) {
|
||||
s.signInCount++
|
||||
s.signInPhone = phone
|
||||
s.signInHash = hash
|
||||
s.signInCode = code
|
||||
if s.signInUser.ID != 0 {
|
||||
return s.signInUser, domain.Message{}, false, nil
|
||||
}
|
||||
return domain.User{}, domain.Message{}, false, nil
|
||||
}
|
||||
|
||||
func (s *captureAuthService) SignInWithEmail(context.Context, domain.Authorization, string, string, string) (domain.User, domain.Message, bool, error) {
|
||||
func (s *captureAuthService) SignInWithEmail(_ context.Context, _ domain.Authorization, phone, hash, code string) (domain.User, domain.Message, bool, error) {
|
||||
s.signInWithEmailCount++
|
||||
s.signInWithEmailPhone = phone
|
||||
s.signInWithEmailHash = hash
|
||||
s.signInWithEmailCode = code
|
||||
if s.signInUser.ID != 0 {
|
||||
return s.signInUser, domain.Message{}, false, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue