mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-23 16:02:14 +00:00
feat: Add SMS country code selector to SMT OTP (LoginV1) (#11373)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved - This adds rudimentary country code handling for LoginV1 SMS OTP - Previously defaulting to CH with no prompt to user causing e.g UK (+44) based numbers to be re-routed to CH. (+41) # Additional Context - Support for SMS OTP is not complete for LoginV2 (according to latest support email) Tested with Twilio on both UK + DE # . Appears to work. - Closes #8891 <img width="544" height="485" alt="image" src="https://github.com/user-attachments/assets/84d31bd8-8863-465f-8860-b51602439dfb" /> <img width="858" height="1298" alt="Screenshot 2026-01-16 at 11 23 22" src="https://github.com/user-attachments/assets/4bf16f0b-2fec-4d63-95b0-f0b73f3a131a" /> --------- Co-authored-by: Livio Spring <[email protected]> Co-authored-by: Silvan <[email protected]>
This commit is contained in:
co-authored by
Livio Spring
Silvan
parent
30d91cde58
commit
597f385ca4
@@ -12,17 +12,20 @@ const (
|
||||
|
||||
type smsInitData struct {
|
||||
userData
|
||||
Edit bool
|
||||
MFAType domain.MFAType
|
||||
Phone string
|
||||
Edit bool
|
||||
MFAType domain.MFAType
|
||||
Phone string
|
||||
CountryCode string
|
||||
CountryCodes []domain.CountryCode
|
||||
}
|
||||
|
||||
type smsInitFormData struct {
|
||||
Edit bool `schema:"edit"`
|
||||
Resend bool `schema:"resend"`
|
||||
Phone string `schema:"phone"`
|
||||
NewPhone string `schema:"newPhone"`
|
||||
Code string `schema:"code"`
|
||||
Edit bool `schema:"edit"`
|
||||
Resend bool `schema:"resend"`
|
||||
Phone string `schema:"phone"`
|
||||
NewPhone string `schema:"newPhone"`
|
||||
Code string `schema:"code"`
|
||||
CountryCode string `schema:"countryCode"`
|
||||
}
|
||||
|
||||
// handleRegisterOTPSMS checks if the user has a verified phone number and will directly add OTP SMS as 2FA.
|
||||
@@ -57,6 +60,10 @@ func (l *Login) renderRegisterSMS(w http.ResponseWriter, r *http.Request, authRe
|
||||
data.baseData = l.getBaseData(r, authReq, translator, "InitMFAOTP.Title", "InitMFAOTP.Description", err)
|
||||
data.profileData = l.getProfileData(authReq)
|
||||
data.MFAType = domain.MFATypeOTPSMS
|
||||
data.CountryCodes = domain.GetCountryCodes(translator.Lang(r))
|
||||
if data.CountryCode == "" {
|
||||
data.CountryCode = "CH" // default to Switzerland
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplMFASMSInit], data, nil)
|
||||
}
|
||||
|
||||
@@ -75,7 +82,7 @@ func (l *Login) handleRegisterSMSCheck(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := setUserContext(r.Context(), authReq.UserID, authReq.UserOrgID)
|
||||
// save the current state
|
||||
data := &smsInitData{Phone: formData.Phone}
|
||||
data := &smsInitData{Phone: formData.Phone, CountryCode: formData.CountryCode}
|
||||
|
||||
if formData.Edit {
|
||||
data.Edit = true
|
||||
@@ -95,7 +102,15 @@ func (l *Login) handleRegisterSMSCheck(w http.ResponseWriter, r *http.Request) {
|
||||
if formData.Code == "" {
|
||||
data.Phone = formData.NewPhone
|
||||
if formData.NewPhone != formData.Phone {
|
||||
_, err = l.command.ChangeUserPhone(ctx, authReq.UserID, formData.NewPhone, l.userCodeAlg)
|
||||
// Normalize phone with selected country code before saving
|
||||
normalizedPhone, normErr := domain.PhoneNumber(formData.NewPhone).NormalizeWithRegion(formData.CountryCode)
|
||||
if normErr != nil {
|
||||
data.Edit = true
|
||||
l.renderRegisterSMS(w, r, authReq, data, normErr)
|
||||
return
|
||||
}
|
||||
data.Phone = string(normalizedPhone)
|
||||
_, err = l.command.ChangeUserPhone(ctx, authReq.UserID, string(normalizedPhone), l.userCodeAlg)
|
||||
if err != nil {
|
||||
// stay in edit more
|
||||
data.Edit = true
|
||||
|
||||
@@ -115,6 +115,7 @@ InitMFAOTPSMS:
|
||||
Title: 2-факторна проверка
|
||||
DescriptionPhone: Създайте своя 2-фактор. Въведете телефонния си номер, за да го потвърдите.
|
||||
DescriptionCode: Създайте своя 2-фактор. Въведете получения код, за да потвърдите своя телефонен номер.
|
||||
CountryCodeLabel: Държава
|
||||
PhoneLabel: Тайна
|
||||
CodeLabel: Код
|
||||
EditButtonText: редактиране
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: 2-Faktorové ověření
|
||||
DescriptionPhone: Vytvořte svou 2-faktorovou autentizaci. Zadejte své telefonní číslo k jeho ověření.
|
||||
DescriptionCode: Vytvořte svou 2-faktorovou autentizaci. Zadejte obdržený kód k ověření vašeho telefonního čísla.
|
||||
CountryCodeLabel: Země
|
||||
PhoneLabel: Telefon
|
||||
CodeLabel: Kód
|
||||
EditButtonText: Upravit
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: Zweitfaktor hinzufügen
|
||||
DescriptionPhone: Erstelle deinen Zweitfaktor. Gib deine Telefonnummer ein, um sie zu verifizieren.
|
||||
DescriptionCode: Erstelle deinen Zweitfaktor. Gib den erhaltenen Code ein, um deine Telefonnummer zu verifizieren.
|
||||
CountryCodeLabel: Land
|
||||
PhoneLabel: Telefonnummer
|
||||
CodeLabel: Code
|
||||
EditButtonText: Bearbeiten
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: 2-Factor Verification
|
||||
DescriptionPhone: Create your 2-factor. Enter your phone number to verify it.
|
||||
DescriptionCode: Create your 2-factor. Enter the received code to verify your phone number.
|
||||
CountryCodeLabel: Country
|
||||
PhoneLabel: Phone
|
||||
CodeLabel: Code
|
||||
EditButtonText: Edit
|
||||
|
||||
@@ -114,10 +114,11 @@ InitMFAOTP:
|
||||
NextButtonText: siguiente
|
||||
CancelButtonText: cancelar
|
||||
|
||||
InitMFASMS:
|
||||
InitMFAOTPSMS:
|
||||
Title: Verificación de doble factor
|
||||
DescriptionPhone: Crea tu doble factor de autenticación. Introduce tu número de teléfono para verificarlo.
|
||||
DescriptionCode: Crea tu doble factor de autenticación. Ingrese el código recibido para verificar su número de teléfono.
|
||||
CountryCodeLabel: País
|
||||
PhoneLabel: Número de teléfono
|
||||
CodeLabel: Código
|
||||
EditButtonText: editar
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: Vérification à deux facteurs
|
||||
DescriptionPhone: Créez votre 2-facteurs. Entrez votre numéro de téléphone pour le vérifier.
|
||||
DescriptionCode: Créez votre 2-facteurs. Entrez le code reçu pour vérifier votre numéro de téléphone.
|
||||
CountryCodeLabel: Pays
|
||||
PhoneLabel: Numéro de téléphone
|
||||
CodeLabel: Code
|
||||
EditButtonText: Modifier
|
||||
|
||||
@@ -105,6 +105,7 @@ InitMFAOTPSMS:
|
||||
Title: Kétfaktoros hitelesítés
|
||||
DescriptionPhone: Hozd létre a kétfaktoros hitelesítést. Add meg a telefonszámod a hitelesítéshez.
|
||||
DescriptionCode: Hozd létre a kétfaktoros hitelesítést. Add meg a kapott kódot a telefonszámod hitelesítéséhez.
|
||||
CountryCodeLabel: Ország
|
||||
PhoneLabel: Telefon
|
||||
CodeLabel: Kód
|
||||
EditButtonText: Szerkesztés
|
||||
|
||||
@@ -105,6 +105,7 @@ InitMFAOTPSMS:
|
||||
Title: Verifikasi 2 Faktor
|
||||
DescriptionPhone: 'Buat 2 faktor Anda. '
|
||||
DescriptionCode: 'Buat 2 faktor Anda. '
|
||||
CountryCodeLabel: Negara
|
||||
PhoneLabel: Telepon
|
||||
CodeLabel: Kode
|
||||
EditButtonText: Sunting
|
||||
|
||||
@@ -114,10 +114,11 @@ InitMFAOTP:
|
||||
NextButtonText: Avanti
|
||||
CancelButtonText: annulla
|
||||
|
||||
InitMFASMS:
|
||||
InitMFAOTPSMS:
|
||||
Title: Verificazione a 2 fattori
|
||||
DescriptionPhone: Crea il tuo 2 fattori. Inserisci il tuo numero di telefono per verificarlo.
|
||||
DescriptionCode: Crea il tuo 2 fattori. Inserisci il codice ricevuto per verificare il tuo numero di telefono.
|
||||
CountryCodeLabel: Paese
|
||||
PhoneLabel: Numero di telefono
|
||||
CodeLabel: Codice
|
||||
EditButtonText: Modifica
|
||||
|
||||
@@ -114,10 +114,11 @@ InitMFAOTP:
|
||||
NextButtonText: 次へ
|
||||
CancelButtonText: キャンセル
|
||||
|
||||
InitMFASMS:
|
||||
InitMFAOTPSMS:
|
||||
Title: 二要素認証
|
||||
DescriptionPhone: 二要素認証を作成します。確認するには電話番号を入力してください。
|
||||
DescriptionCode: 二要素認証を作成します。受信したコードを入力して電話番号を確認します。
|
||||
CountryCodeLabel: 国
|
||||
PhoneLabel: 電話番号
|
||||
CodeLabel: コード
|
||||
EditButtonText: 編集
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: 2단계 인증
|
||||
DescriptionPhone: 2단계 인증을 설정하세요. 전화번호를 입력하여 인증하세요.
|
||||
DescriptionCode: 2단계 인증을 설정하세요. 받은 코드를 입력하여 전화번호를 인증하세요.
|
||||
CountryCodeLabel: 국가
|
||||
PhoneLabel: 전화번호
|
||||
CodeLabel: 코드
|
||||
EditButtonText: 수정
|
||||
|
||||
@@ -114,10 +114,11 @@ InitMFAOTP:
|
||||
NextButtonText: следно
|
||||
CancelButtonText: откажи
|
||||
|
||||
InitMFASMS:
|
||||
InitMFAOTPSMS:
|
||||
Title: Потврда на 2-факторска автентикација
|
||||
DescriptionPhone: Направете двофакторна автентикација. Внесете го вашиот телефонски број за да го потврдите.
|
||||
DescriptionCode: Направете двофакторна автентикација. Внесете го примениот код за да го потврдите вашиот телефонски број.
|
||||
CountryCodeLabel: Земја
|
||||
PhoneLabel: Телефонски број
|
||||
CodeLabel: Код
|
||||
EditButtonText: Уредување
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: 2-Factor Verificatie
|
||||
DescriptionPhone: Maak uw 2-factor aan. Voer uw telefoonnummer in om te verifiëren.
|
||||
DescriptionCode: Maak uw 2-factor aan. Voer de ontvangen code in om uw telefoonnummer te verifiëren.
|
||||
CountryCodeLabel: Land
|
||||
PhoneLabel: Telefoon
|
||||
CodeLabel: Code
|
||||
EditButtonText: Bewerken
|
||||
|
||||
@@ -114,10 +114,11 @@ InitMFAOTP:
|
||||
NextButtonText: dalej
|
||||
CancelButtonText: anuluj
|
||||
|
||||
InitMFASMS:
|
||||
InitMFAOTPSMS:
|
||||
Title: Weryfikacja 2-etapowa
|
||||
DescriptionPhone: Utwórz uwierzytelnianie dwuskładnikowe. Wprowadź swój numer telefonu, aby go zweryfikować.
|
||||
DescriptionCode: Utwórz uwierzytelnianie dwuskładnikowe. Wprowadź otrzymany kod, aby zweryfikować swój numer telefonu.
|
||||
CountryCodeLabel: Kraj
|
||||
PhoneLabel: Numer telefonu
|
||||
CodeLabel: Kod
|
||||
EditButtonText: edytować
|
||||
|
||||
@@ -114,10 +114,11 @@ InitMFAOTP:
|
||||
NextButtonText: próximo
|
||||
CancelButtonText: cancelar
|
||||
|
||||
InitMFASMS:
|
||||
InitMFAOTPSMS:
|
||||
Title: Verificação de 2 fatores
|
||||
DescriptionPhone: Crie sua verificação de 2 fatores. Digite seu número de telefone para verificá-lo.
|
||||
DescriptionCode: Crie sua verificação de 2 fatores. Digite o código recebido para verificar seu número de telefone.
|
||||
CountryCodeLabel: País
|
||||
PhoneLabel: Número de telefone
|
||||
CodeLabel: Código
|
||||
EditButtonText: editar
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: Verificare în 2 pași
|
||||
DescriptionPhone: Creează-ți autentificarea cu 2 factori. Introdu numărul tău de telefon pentru a-l verifica.
|
||||
DescriptionCode: Creează-ți autentificarea cu 2 factori. Introdu codul primit pentru a-ți verifica numărul de telefon.
|
||||
CountryCodeLabel: Țară
|
||||
PhoneLabel: Telefon
|
||||
CodeLabel: Cod
|
||||
EditButtonText: Editează
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: Настройка двухфакторной аутентификации
|
||||
DescriptionPhone: Введите номер телефона для его подтверждения.
|
||||
DescriptionCode: Введите код из СМС для подтверждения номера телефона.
|
||||
CountryCodeLabel: Страна
|
||||
PhoneLabel: Номер телефона
|
||||
CodeLabel: Код из СМС
|
||||
EditButtonText: Изменить
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: Tvåfaktor verifiering
|
||||
DescriptionPhone: Verifiera med en andra faktor. Ange ditt mobilnummer.
|
||||
DescriptionCode: Verifiera med en andra faktor. Ange koden du fått på SMS för att bekräfta ditt mobilnr.
|
||||
CountryCodeLabel: Land
|
||||
PhoneLabel: Mobilnr
|
||||
CodeLabel: Kod
|
||||
EditButtonText: Ändra
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: 2-Faktör Doğrulama
|
||||
DescriptionPhone: 2-faktörünüzü oluşturun. Doğrulamak için telefon numaranızı girin.
|
||||
DescriptionCode: 2-faktörünüzü oluşturun. Telefon numaranızı doğrulamak için aldığınız kodu girin.
|
||||
CountryCodeLabel: Ülke
|
||||
PhoneLabel: Telefon
|
||||
CodeLabel: Kod
|
||||
EditButtonText: Düzenle
|
||||
|
||||
@@ -118,6 +118,7 @@ InitMFAOTPSMS:
|
||||
Title: 2-факторна перевірка
|
||||
DescriptionPhone: Створіть вашу 2-факторну автентифікацію. Введіть ваш номер телефону для підтвердження.
|
||||
DescriptionCode: Створіть вашу 2-факторну автентифікацію. Введіть отриманий код для підтвердження вашого номера телефону.
|
||||
CountryCodeLabel: Країна
|
||||
PhoneLabel: Телефон
|
||||
CodeLabel: Код
|
||||
EditButtonText: Редагувати
|
||||
|
||||
@@ -109,21 +109,22 @@ InitMFAOTP:
|
||||
Title: 双因素验证
|
||||
Description: 创建你的双因素。如果你还没有,请下载一个认证器应用程序。
|
||||
OTPDescription: 使用您的身份验证器应用程序(例如 Google Authenticator)扫描代码或复制密码并在下方插入生成的代码。
|
||||
SecretLabel: 秘钥
|
||||
CodeLabel: 验证码
|
||||
NextButtonText: 继续
|
||||
CancelButtonText: 取消
|
||||
|
||||
InitMFAOTPSMS:
|
||||
Title: 双因素验证
|
||||
DescriptionPhone: 创建双因素身份验证。输入您的电话号码进行验证。
|
||||
DescriptionCode: 创建双因素身份验证。输入收到的代码以验证您的电话号码。
|
||||
CountryCodeLabel: 国家
|
||||
PhoneLabel: 电话号码
|
||||
CodeLabel: 验证码
|
||||
EditButtonText: 编辑
|
||||
ResendButtonText: 重发代码
|
||||
NextButtonText: 继续
|
||||
|
||||
InitMFASMS:
|
||||
Title: 双因素验证
|
||||
DescriptionPhone: 创建双因素身份验证。输入您的电话号码进行验证。
|
||||
DescriptionCode: 创建双因素身份验证。输入收到的代码以验证您的电话号码。
|
||||
SecretLabel: 秘钥
|
||||
CodeLabel: 验证码
|
||||
NextButtonText: 继续
|
||||
CancelButtonText: 取消
|
||||
|
||||
InitMFAU2F:
|
||||
Title: 添加安全密钥
|
||||
Description: 安全密钥是一种验证方法,可以内置在手机中、使用蓝牙或直接插入计算机的 USB 端口。
|
||||
|
||||
@@ -22,15 +22,24 @@
|
||||
|
||||
<div class="fields">
|
||||
{{if .Edit}}
|
||||
<div class="field">
|
||||
<label class="lgn-label" for="countryCode">{{t "InitMFAOTPSMS.CountryCodeLabel"}}</label>
|
||||
<select class="lgn-input" id="countryCode" name="countryCode" required>
|
||||
{{range .CountryCodes}}
|
||||
<option value="{{.ISOCode}}" {{if eq .ISOCode $.CountryCode}}selected{{end}}>{{.Name}} ({{.CallingCode}})</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="lgn-label" for="newPhone">{{t "InitMFAOTPSMS.PhoneLabel"}}</label>
|
||||
<input class="lgn-input" type="tel" id="newPhone" name="newPhone" autocomplete="off" value="{{.Phone}}" autofocus required>
|
||||
</div>
|
||||
{{else}}
|
||||
<input type="hidden" name="countryCode" value="{{ .CountryCode }}" />
|
||||
<div class="field lgn-actions">
|
||||
<p>{{.Phone}}</p>
|
||||
<span class="fill-space"></span>
|
||||
<button type="button" id="edit" name="edit" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.EditButtonText"}}</button>
|
||||
<button type="submit" id="edit" name="edit" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.EditButtonText"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Phone (not .Edit) }}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ttacon/libphonenumber"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/language/display"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
@@ -15,10 +19,17 @@ const defaultRegion = "CH"
|
||||
type PhoneNumber string
|
||||
|
||||
func (p PhoneNumber) Normalize() (PhoneNumber, error) {
|
||||
return p.NormalizeWithRegion(defaultRegion)
|
||||
}
|
||||
|
||||
func (p PhoneNumber) NormalizeWithRegion(region string) (PhoneNumber, error) {
|
||||
if p == "" {
|
||||
return p, zerrors.ThrowInvalidArgument(nil, "PHONE-Zt0NV", "Errors.User.Phone.Empty")
|
||||
}
|
||||
phoneNr, err := libphonenumber.Parse(string(p), defaultRegion)
|
||||
if region == "" {
|
||||
region = defaultRegion
|
||||
}
|
||||
phoneNr, err := libphonenumber.Parse(string(p), region)
|
||||
if err != nil {
|
||||
return p, zerrors.ThrowInvalidArgument(err, "PHONE-so0wa", "Errors.User.Phone.Invalid")
|
||||
}
|
||||
@@ -71,3 +82,44 @@ func (s PhoneState) Valid() bool {
|
||||
func (s PhoneState) Exists() bool {
|
||||
return s == PhoneStateActive
|
||||
}
|
||||
|
||||
// CountryCode represents a country with its ISO code and calling code
|
||||
type CountryCode struct {
|
||||
ISOCode string
|
||||
CallingCode string
|
||||
Name string
|
||||
}
|
||||
|
||||
// GetCountryCodes returns a list of country codes for phone number input using libphonenumber
|
||||
func GetCountryCodes(lang language.Tag) []CountryCode {
|
||||
regions := libphonenumber.GetSupportedRegions()
|
||||
namer := display.Regions(lang)
|
||||
|
||||
codes := make([]CountryCode, 0, len(regions))
|
||||
for region := range regions {
|
||||
countryCode := libphonenumber.GetCountryCodeForRegion(region)
|
||||
if countryCode == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the display name for the region
|
||||
regionTag, err := language.ParseRegion(region)
|
||||
name := region
|
||||
if err == nil {
|
||||
name = namer.Name(regionTag)
|
||||
}
|
||||
|
||||
codes = append(codes, CountryCode{
|
||||
ISOCode: region,
|
||||
CallingCode: fmt.Sprintf("+%d", countryCode),
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort by country name
|
||||
sort.Slice(codes, func(i, j int) bool {
|
||||
return codes[i].Name < codes[j].Name
|
||||
})
|
||||
|
||||
return codes
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
@@ -104,3 +108,116 @@ func TestFormatPhoneNumber(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCountryCodes(t *testing.T) {
|
||||
codes := GetCountryCodes(language.English)
|
||||
|
||||
t.Run("returns non-empty list", func(t *testing.T) {
|
||||
if len(codes) == 0 {
|
||||
t.Error("GetCountryCodes() returned empty list")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("returns more than 200 countries", func(t *testing.T) {
|
||||
// libphonenumber supports 240+ regions
|
||||
if len(codes) < 200 {
|
||||
t.Errorf("GetCountryCodes() returned %d countries, expected at least 200", len(codes))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("list is sorted by name", func(t *testing.T) {
|
||||
sorted := sort.SliceIsSorted(codes, func(i, j int) bool {
|
||||
return codes[i].Name < codes[j].Name
|
||||
})
|
||||
if !sorted {
|
||||
t.Error("GetCountryCodes() did not return a list sorted by name")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("all entries have valid format", func(t *testing.T) {
|
||||
for _, code := range codes {
|
||||
if code.ISOCode == "" {
|
||||
t.Errorf("country code has empty ISOCode: %+v", code)
|
||||
}
|
||||
if len(code.ISOCode) < 2 {
|
||||
t.Errorf("country code has invalid ISOCode length: %+v", code)
|
||||
}
|
||||
if !strings.HasPrefix(code.CallingCode, "+") {
|
||||
t.Errorf("country code CallingCode does not start with '+': %+v", code)
|
||||
}
|
||||
if code.Name == "" {
|
||||
t.Errorf("country code has empty Name: %+v", code)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetCountryCodes_ContainsExpectedCountries(t *testing.T) {
|
||||
codes := GetCountryCodes(language.English)
|
||||
|
||||
// Build a map for easier lookup
|
||||
codeMap := make(map[string]CountryCode)
|
||||
for _, code := range codes {
|
||||
codeMap[code.ISOCode] = code
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
isoCode string
|
||||
callingCode string
|
||||
countryName string
|
||||
}{
|
||||
{
|
||||
name: "United States",
|
||||
isoCode: "US",
|
||||
callingCode: "+1",
|
||||
countryName: "United States",
|
||||
},
|
||||
{
|
||||
name: "Switzerland",
|
||||
isoCode: "CH",
|
||||
callingCode: "+41",
|
||||
countryName: "Switzerland",
|
||||
},
|
||||
{
|
||||
name: "United Kingdom",
|
||||
isoCode: "GB",
|
||||
callingCode: "+44",
|
||||
countryName: "United Kingdom",
|
||||
},
|
||||
{
|
||||
name: "Germany",
|
||||
isoCode: "DE",
|
||||
callingCode: "+49",
|
||||
countryName: "Germany",
|
||||
},
|
||||
{
|
||||
name: "Australia",
|
||||
isoCode: "AU",
|
||||
callingCode: "+61",
|
||||
countryName: "Australia",
|
||||
},
|
||||
{
|
||||
name: "Japan",
|
||||
isoCode: "JP",
|
||||
callingCode: "+81",
|
||||
countryName: "Japan",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
code, found := codeMap[tt.isoCode]
|
||||
if !found {
|
||||
t.Errorf("GetCountryCodes() does not contain %s (%s)", tt.name, tt.isoCode)
|
||||
return
|
||||
}
|
||||
if code.CallingCode != tt.callingCode {
|
||||
t.Errorf("GetCountryCodes() %s CallingCode = %s, want %s", tt.isoCode, code.CallingCode, tt.callingCode)
|
||||
}
|
||||
if code.Name != tt.countryName {
|
||||
t.Errorf("GetCountryCodes() %s Name = %s, want %s", tt.isoCode, code.Name, tt.countryName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user