# frozen_string_literal: true require 'rails_helper' require 'user_name_suggester' describe UserNameSuggester do describe '.suggest' do before do SiteSetting.min_username_length = 3 SiteSetting.max_username_length = 15 SiteSetting.reserved_usernames = '' end it "keeps adding numbers to the username" do Fabricate(:user, username: 'sam') Fabricate(:user, username: 'sAm1') Fabricate(:user, username: 'sam2') Fabricate(:user, username: 'sam4') expect(UserNameSuggester.suggest('saM')).to eq('saM3') end it "doesn't raise an error on nil username and suggest the fallback username" do expect(UserNameSuggester.suggest(nil)).to eq(I18n.t('fallback_username')) end it "doesn't raise an error on integer username" do expect(UserNameSuggester.suggest(999)).to eq('999') end it 'corrects weird characters' do expect(UserNameSuggester.suggest("Darth%^Vader")).to eq('Darth_Vader') end it 'adds 1 to an existing username' do user = Fabricate(:user) expect(UserNameSuggester.suggest(user.username)).to eq("#{user.username}1") end it "adds numbers if it's too short" do expect(UserNameSuggester.suggest('a')).to eq('a11') end it 'is able to guess a decent username from an email' do expect(UserNameSuggester.suggest('bob@example.com')).to eq('bob') end it "has a special case for me and i emails" do expect(UserNameSuggester.suggest('me@eviltrout.com')).to eq('eviltrout') expect(UserNameSuggester.suggest('i@eviltrout.com')).to eq('eviltrout') end it "shortens very long suggestions" do expect(UserNameSuggester.suggest("myreallylongnameisrobinwardesquire")).to eq('myreallylongnam') end it "makes room for the digit added if the username is too long" do User.create(username: 'myreallylongnam', email: 'fake@discourse.org') expect(UserNameSuggester.suggest("myreallylongnam")).to eq('myreallylongna1') end it "doesn't suggest reserved usernames" do SiteSetting.reserved_usernames = 'myadmin|steve|steve1' expect(UserNameSuggester.suggest("myadmin@hissite.com")).to eq('myadmin1') expect(UserNameSuggester.suggest("steve")).to eq('steve2') end it "doesn't suggest generic usernames" do UserNameSuggester::GENERIC_NAMES.each do |name| expect(UserNameSuggester.suggest("#{name}@apple.org")).to eq('apple') end end it "removes leading character if it is not alphanumeric" do expect(UserNameSuggester.suggest(".myname")).to eq('myname') end it "allows leading _" do expect(UserNameSuggester.suggest("_myname")).to eq('_myname') end it "removes trailing characters if they are invalid" do expect(UserNameSuggester.suggest("myname!^$=")).to eq('myname') end it "suggest a fallback username if name contains only invalid characters" do suggestion = UserNameSuggester.suggest("---") expect(suggestion).to eq(I18n.t('fallback_username')) end it "allows dots in the middle" do expect(UserNameSuggester.suggest("my.name")).to eq('my.name') end it "remove leading dots" do expect(UserNameSuggester.suggest(".myname")).to eq('myname') end it "remove trailing dots" do expect(UserNameSuggester.suggest("myname.")).to eq('myname') end it 'handles usernames with a sequence of 2 or more special chars' do expect(UserNameSuggester.suggest('Darth__Vader')).to eq('Darth_Vader') expect(UserNameSuggester.suggest('Darth_-_Vader')).to eq('Darth_Vader') end it 'should handle typical facebook usernames' do expect(UserNameSuggester.suggest('roger.nelson.3344913')).to eq('roger.nelson.33') end it 'removes underscore at the end of long usernames that get truncated' do expect(UserNameSuggester.suggest('uuuuuuuuuuuuuu_u')).to_not end_with('_') end it "adds number if it's too short after removing trailing underscore" do User.stubs(:username_length).returns(8..8) expect(UserNameSuggester.suggest('uuuuuuu_u')).to eq('uuuuuuu1') end context "with Unicode usernames disabled" do before { SiteSetting.unicode_usernames = false } it "transliterates some characters" do expect(UserNameSuggester.suggest('Jørn')).to eq('Jorn') end it "uses fallback username if there are Unicode characters only" do fallback_username = I18n.t('fallback_username') expect(UserNameSuggester.suggest('طائر')).to eq(fallback_username) expect(UserNameSuggester.suggest('πουλί')).to eq(fallback_username) end end context "with Unicode usernames enabled" do before { SiteSetting.unicode_usernames = true } it "normalizes unicode usernames with Σ to lowercase" do expect(UserNameSuggester.suggest('ΣΣ\'"ΣΣ')).to eq('σς_σς') end it "does not transliterate" do expect(UserNameSuggester.suggest("Jørn")).to eq('Jørn') end it "does not replace Unicode characters" do expect(UserNameSuggester.suggest('طائر')).to eq('طائر') expect(UserNameSuggester.suggest('πουλί')).to eq('πουλί') end it "shortens usernames by counting grapheme clusters" do SiteSetting.max_username_length = 10 expect(UserNameSuggester.suggest('बहुत-लंबा-उपयोगकर्ता-नाम')).to eq('बहुत-लंबा-उपयो') end it "adds numbers if it's too short" do expect(UserNameSuggester.suggest('鳥')).to eq('鳥11') # grapheme cluster consists of 3 code points expect(UserNameSuggester.suggest('য়া')).to eq('য়া11') end it "does not skip ove allowed names" do Fabricate(:user, username: 'sam') Fabricate(:user, username: 'saM1') Fabricate(:user, username: 'sam2') expect(UserNameSuggester.suggest('SaM', 'Sam1')).to eq('Sam1') end it "normalizes usernames" do actual = 'Löwe' # NFD, "Lo\u0308we" expected = 'Löwe' # NFC, "L\u00F6we" expect(UserNameSuggester.suggest(actual)).to eq(expected) end it "does not suggest a username longer than max column size" do SiteSetting.max_username_length = 40 expect(UserNameSuggester.suggest('য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া')) .to eq('য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া-য়া') end it "uses allowlist" do SiteSetting.allowed_unicode_username_characters = "[äöüßÄÖÜẞ]" expect(UserNameSuggester.suggest('πουλί')).to eq(I18n.t('fallback_username')) expect(UserNameSuggester.suggest('a鳥b')).to eq('a_b') expect(UserNameSuggester.suggest('Löwe')).to eq('Löwe') SiteSetting.allowed_unicode_username_characters = "[য়া]" expect(UserNameSuggester.suggest('aয়াb鳥c')).to eq('aয়াb_c') end end end end