# frozen_string_literal: true describe UsernameValidator do def expect_valid(*usernames) usernames.each do |username| validator = UsernameValidator.new(username) aggregate_failures do expect(validator.valid_format?).to eq(true), "expected '#{username}' to be valid" expect(validator.errors).to be_empty end end end def expect_invalid(*usernames, error_message:) usernames.each do |username| validator = UsernameValidator.new(username) aggregate_failures do expect(validator.valid_format?).to eq(false), "expected '#{username}' to be invalid" expect(validator.errors).to include(error_message) end end end shared_examples 'ASCII username' do it 'is invalid when the username is blank' do expect_invalid('', error_message: I18n.t(:'user.username.blank')) end it 'is invalid when the username is too short' do SiteSetting.min_username_length = 4 expect_invalid('a', 'ab', 'abc', error_message: I18n.t(:'user.username.short', min: 4)) end it 'is valid when the username has the minimum length' do SiteSetting.min_username_length = 4 expect_valid('abcd') end it 'is invalid when the username is too long' do SiteSetting.max_username_length = 8 expect_invalid('abcdefghi', error_message: I18n.t(:'user.username.long', max: 8)) end it 'is valid when the username has the maximum length' do SiteSetting.max_username_length = 8 expect_valid('abcdefgh') end it 'is valid when the username contains alphanumeric characters, dots, underscores and dashes' do expect_valid('ab-cd.123_ABC-xYz') end it 'is invalid when the username contains non-alphanumeric characters other than dots, underscores and dashes' do expect_invalid('abc|', 'a#bc', 'abc xyz', error_message: I18n.t(:'user.username.characters')) end it 'is valid when the username starts with a alphanumeric character or underscore' do expect_valid('abcd', '1abc', '_abc') end it 'is invalid when the username starts with a dot or dash' do expect_invalid('.abc', '-abc', error_message: I18n.t(:'user.username.must_begin_with_alphanumeric_or_underscore')) end it 'is valid when the username ends with a alphanumeric character' do expect_valid('abcd', 'abc9') end it 'is invalid when the username ends with an underscore, a dot or dash' do expect_invalid('abc_', 'abc.', 'abc-', error_message: I18n.t(:'user.username.must_end_with_alphanumeric')) end it 'is invalid when the username contains consecutive underscores, dots or dashes' do expect_invalid('a__bc', 'a..bc', 'a--bc', error_message: I18n.t(:'user.username.must_not_contain_two_special_chars_in_seq')) end it 'is invalid when the username ends with certain file extensions' do expect_invalid('abc.json', 'abc.png', error_message: I18n.t(:'user.username.must_not_end_with_confusing_suffix')) end end context 'when Unicode usernames are disabled' do before { SiteSetting.unicode_usernames = false } include_examples 'ASCII username' it 'is invalid when the username contains non-ASCII characters except dots, underscores and dashes' do expect_invalid('abcö', 'abc象', error_message: I18n.t(:'user.username.characters')) end end context 'when Unicode usernames are enabled' do before { SiteSetting.unicode_usernames = true } context "ASCII usernames" do include_examples 'ASCII username' end context "Unicode usernames" do before { SiteSetting.min_username_length = 1 } it 'is invalid when the username is too short' do SiteSetting.min_username_length = 3 expect_invalid('鳥', 'পাখি', error_message: I18n.t(:'user.username.short', min: 3)) end it 'is valid when the username has the minimum length' do SiteSetting.min_username_length = 2 expect_valid('পাখি', 'طائر') end it 'is invalid when the username is too long' do SiteSetting.max_username_length = 8 expect_invalid('חוטב_עצים', 'Holzfäller', error_message: I18n.t(:'user.username.long', max: 8)) end it 'is valid when the username has the maximum length' do SiteSetting.max_username_length = 9 expect_valid('Дровосек', 'چوب-لباسی', 'தமிழ்-தமிழ்') end it 'is invalid when the username has too many Unicode codepoints' do SiteSetting.max_username_length = 30 expect_invalid('য়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়া', error_message: I18n.t(:'user.username.too_long')) end it 'is valid when the username contains Unicode letters' do expect_valid('鳥', 'طائر', 'թռչուն', 'πουλί', 'পাখি', 'madár', '새', 'پرنده', 'птица', 'fågel', 'นก', 'پرندے', 'ציפור') end it 'is valid when the username contains numbers from the Nd or Nl Unicode category' do expect_valid('arabic٠١٢٣٤٥٦٧٨٩', 'bengali০১২৩৪৫৬৭৮৯', 'romanⅥ', 'hangzhou〺') end it 'is invalid when the username contains numbers from the No Unicode category' do expect_invalid('circled㊸', 'fraction¾', error_message: I18n.t(:'user.username.characters')) end it 'is invalid when the username contains symbols or emojis' do SiteSetting.min_username_length = 1 expect_invalid('©', '⇨', '“', '±', '‿', '😃', '🚗', error_message: I18n.t(:'user.username.characters')) end it 'is invalid when the username contains zero width join characters' do expect_invalid('ണ്‍', 'র‌্যাম', error_message: I18n.t(:'user.username.characters')) end it 'is valid when the username ends with a Unicode Mark' do expect_valid('தமிழ்') end it 'allows all Unicode letters when the allowlist is empty' do expect_valid('鳥') end context "with Unicode allowlist" do before { SiteSetting.allowed_unicode_username_characters = "[äöüÄÖÜß]" } it 'is invalid when username contains non-allowlisted letters' do expect_invalid('鳥', 'francès', error_message: I18n.t(:'user.username.characters')) end it 'is valid when username contains only allowlisted letters' do expect_valid('Löwe', 'Ötzi') end it 'is valid when username contains only ASCII letters and numbers regardless of allowlist' do expect_valid('a-z_A-Z.0-9') end it 'is valid after resetting the site setting' do SiteSetting.allowed_unicode_username_characters = "" expect_valid('鳥') end end end end end