Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to disable digits in Faker::Internet.password method #3033

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions lib/faker/default/internet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def username(specifier: nil, separators: %w[. _])
# @param max_length [Integer] The maximum length of the password
# @param mix_case [Boolean] Toggles if uppercased letters are allowed. If true, at least one will be added.
# @param special_characters [Boolean] Toggles if special characters are allowed. If true, at least one will be added.
# @param digits [Boolean] Toggles if digits are allowed. If true, at least one will be added.
#
# @return [String]
#
Expand All @@ -121,9 +122,13 @@ def username(specifier: nil, separators: %w[. _])
# Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true) #=> "3k5qS15aNmG"
# @example
# Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true, special_characters: true) #=> "*%NkOnJsH4"
# @example
# Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true, special_characters: true, digits: true) #=> "$f%Ym*sc91e4O^"
# @example
# Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true, special_characters: true, digits: false) #=> "rI$XYZgmBONFKWIZ$B"
#
# @faker.version 2.1.3
def password(min_length: 8, max_length: 16, mix_case: true, special_characters: false)
def password(min_length: 8, max_length: 16, mix_case: true, special_characters: false, digits: true)
raise ArgumentError, 'min_length and max_length must be greater than or equal to one' if min_length < 1 || max_length < 1
raise ArgumentError, 'min_length must be smaller than or equal to max_length' unless min_length <= max_length

Expand All @@ -140,6 +145,11 @@ def password(min_length: 8, max_length: 16, mix_case: true, special_characters:
required_min_length += 1
end

if digits
character_types << :digits
required_min_length += 1
end

raise ArgumentError, "min_length should be at least #{required_min_length} to enable #{character_types.join(', ')} configuration" if min_length < required_min_length

target_length = rand(min_length..max_length)
Expand All @@ -152,10 +162,6 @@ def password(min_length: 8, max_length: 16, mix_case: true, special_characters:
password << sample(lower_chars)
character_bag += lower_chars

digits = ('0'..'9').to_a
password << sample(digits)
character_bag += digits

if mix_case
upper_chars = self::ULetters
password << sample(upper_chars)
Expand All @@ -168,6 +174,12 @@ def password(min_length: 8, max_length: 16, mix_case: true, special_characters:
character_bag += special_chars
end

if digits
digits_chars = ('0'..'9').to_a
password << sample(digits_chars)
character_bag += digits_chars
end

password << sample(character_bag) while password.length < target_length

shuffle(password).join
Expand Down
70 changes: 49 additions & 21 deletions test/faker/default/test_faker_internet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ def test_password_with_min_length_eq_1_without_mix_case
end

def test_password_with_min_length_and_max_length
min_length = 2
max_length = 5
min_length = 3
max_length = 6
password = @tester.password(min_length: min_length, max_length: max_length)

assert_match(/\w+/, password)
Expand Down Expand Up @@ -265,34 +265,45 @@ def test_password_without_special_chars
assert_match(/[^!@#$%\^&*]+/, @tester.password(min_length: 8, max_length: 12, mix_case: true))
end

def test_password_with_special_chars_and_mixed_case
def test_password_with_digits
assert_match(/[0-9]+/, @tester.password(min_length: 8, max_length: 12, digits: true))
end

def test_password_without_digits
assert_match(/^[^0-9]+$/, @tester.password(min_length: 8, max_length: 12, digits: false))
end

def test_password_with_special_chars_and_mixed_case_and_digits
32.times do
password = @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true)
password = @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true, digits: true)

assert_match(/[!@#$%\^&*]+/, password)
assert_match(/[A-z]+/, password)
assert_match(/[0-9]+/, password)
end
end

def test_deterministic_password_with_special_chars_and_mixed_case
deterministically_verify -> { @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true) }, depth: 4 do |password|
def test_deterministic_password_with_special_chars_and_mixed_case_and_digits
deterministically_verify -> { @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true, digits: true) }, depth: 4 do |password|
assert_match(/[!@#$%\^&*]+/, password)
assert_match(/[A-z]+/, password)
assert_match(/[0-9]+/, password)
end
end

def test_password_with_special_chars_and_mixed_case_on_3chars_password
def test_password_with_special_chars_and_mixed_case_and_digits_on_4chars_password
16.times do
password = @tester.password(min_length: 3, max_length: 6, mix_case: true, special_characters: true)
password = @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true, digits: true)

assert_match(/[!@#$%\^&*]+/, password)
assert_match(/[A-z]+/, password)
assert_match(/[0-9]+/, password)
end
end

def test_password_with_invalid_min_length_for_mix_case_and_special_characters
assert_raise_message 'min_length should be at least 3 to enable mix_case, special_characters configuration' do
@tester.password(min_length: 1, mix_case: true, special_characters: true)
def test_password_with_invalid_min_length_for_mix_case_and_special_characters_and_digits
assert_raise_message 'min_length should be at least 4 to enable mix_case, special_characters, digits configuration' do
@tester.password(min_length: 1, mix_case: true, special_characters: true, digits: true)
end
end

Expand All @@ -306,35 +317,52 @@ def test_password_with_invalid_min_max_length

def test_password_with_invalid_min_length_for_special_characters_only
error = assert_raises(ArgumentError) do
@tester.password(min_length: 0, mix_case: false, special_characters: true)
@tester.password(min_length: 0, mix_case: false, special_characters: true, digits: false)
end

assert_equal 'min_length and max_length must be greater than or equal to one', error.message
end

def test_password_with_invalid_min_length_for_mix_case_only
error = assert_raises(ArgumentError) do
@tester.password(min_length: 1, mix_case: true)
@tester.password(min_length: 1, mix_case: true, special_characters: false, digits: false)
end

assert_equal 'min_length should be at least 2 to enable mix_case configuration', error.message
end

def test_password_with_invalid_min_length_for_digits_only
error = assert_raises(ArgumentError) do
@tester.password(min_length: 0, mix_case: false, special_characters: false, digits: true)
end

assert_equal 'min_length and max_length must be greater than or equal to one', error.message
end

def test_password_with_compatible_min_length_and_requirements
assert_nothing_raised do
[false, true].each do |value|
min_length = value ? 2 : 1
@tester.password(min_length: min_length, mix_case: value, special_characters: !value)
[false, true].each do |mix_case|
[false, true].each do |special_characters|
[false, true].each do |digits|
min_length = [[mix_case ? 2 : 0, special_characters ? 1 : 0, digits ? 1 : 0].sum, 1].max

@tester.password(min_length: min_length, mix_case: mix_case, special_characters: special_characters, digits: digits)
end
end
end
end
end

def test_deterministic_password_with_compatible_min_length_and_requirements
[false, true].each do |value|
min_length = value ? 2 : 1

deterministically_verify -> { @tester.password(min_length: min_length, mix_case: value, special_characters: !value) }, depth: 4 do |password|
assert_nothing_raised { password }
[false, true].each do |mix_case|
[false, true].each do |special_characters|
[false, true].each do |digits|
min_length = [[mix_case ? 2 : 0, special_characters ? 1 : 0, digits ? 1 : 0].sum, 1].max

deterministically_verify -> { @tester.password(min_length: min_length, mix_case: mix_case, special_characters: special_characters, digits: digits) }, depth: 4 do |password|
assert_nothing_raised { password }
end
end
end
end
end
Expand Down