Skip to content

Commit

Permalink
Allow to forbid digits in Faker::Internet.password method
Browse files Browse the repository at this point in the history
This change allows to allow or not digits inside the password. If `digits: true`, a digit is automatically added.
  • Loading branch information
francktrouillez committed Dec 6, 2024
1 parent b9ef4e9 commit 2313e3f
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 32 deletions.
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
76 changes: 49 additions & 27 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,39 @@ 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(/^[A-Za-z0-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|
assert_match(/[!@#$%\^&*]+/, password)
assert_match(/[A-z]+/, 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(/^[A-Za-z0-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(/^[A-Za-z0-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 +311,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

0 comments on commit 2313e3f

Please sign in to comment.