Skip to content

Commit cd273e8

Browse files
committed
Tests: comprehensive bcrypt coverage and DRY helpers across suite
- Add model-level bcrypt tests: creation, hash validity/format, masked token - Add authenticator tests for bcrypt path incl. known-prefix fallback - Introduce with_hash_strategy helper to DRY config toggling in tests - Keep sha256 behavior verified as default and explicit config Motivation: ensure bcrypt configuration is fully exercised end-to-end (Digestor, ApiKey model, and Authenticator), aligned with README guidance. This strengthens production readiness when switching to bcrypt.
1 parent 8212371 commit cd273e8

File tree

3 files changed

+86
-7
lines changed

3 files changed

+86
-7
lines changed

test/models/api_key_test.rb

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,43 @@ def setup
5252
end
5353

5454
test "creates with sha256 digest if configured" do
55-
original_strategy = ApiKeys.configuration.hash_strategy
56-
ApiKeys.configuration.hash_strategy = :sha256
57-
api_key = ApiKeys::ApiKey.create!(owner: @user, name: "SHA256 Key")
58-
assert_equal "sha256", api_key.digest_algorithm
59-
assert ApiKeys::Services::Digestor.match?(token: api_key.instance_variable_get(:@token), stored_digest: api_key.token_digest, strategy: :sha256)
60-
ensure
61-
ApiKeys.configuration.hash_strategy = original_strategy
55+
with_hash_strategy(:sha256) do
56+
api_key = ApiKeys::ApiKey.create!(owner: @user, name: "SHA256 Key")
57+
assert_equal "sha256", api_key.digest_algorithm
58+
assert ApiKeys::Services::Digestor.match?(token: api_key.instance_variable_get(:@token), stored_digest: api_key.token_digest, strategy: :sha256)
59+
end
60+
end
61+
62+
# === Bcrypt strategy ===
63+
test "creates with bcrypt digest if configured" do
64+
with_hash_strategy(:bcrypt) do
65+
api_key = ApiKeys::ApiKey.create!(owner: @user, name: "BCrypt Key")
66+
assert_equal "bcrypt", api_key.digest_algorithm
67+
assert BCrypt::Password.valid_hash?(api_key.token_digest)
68+
assert BCrypt::Password.new(api_key.token_digest) == api_key.instance_variable_get(:@token)
69+
end
70+
end
71+
72+
test "bcrypt digest format and length look valid" do
73+
with_hash_strategy(:bcrypt) do
74+
api_key = ApiKeys::ApiKey.create!(owner: @user, name: "BCrypt Format Key")
75+
digest = api_key.token_digest
76+
# Typical bcrypt hashes look like: $2a$12$... or $2b$...
77+
assert_match(/^\$2[aby]\$\d{2}\$/ , digest)
78+
assert_operator digest.length, :>=, 55 # common bcrypt length ~60
79+
end
80+
end
81+
82+
test "masked_token and last4 correct under bcrypt" do
83+
with_hash_strategy(:bcrypt) do
84+
api_key = ApiKeys::ApiKey.create!(owner: @user, name: "BCrypt Masked")
85+
token = api_key.instance_variable_get(:@token)
86+
random_part = token.delete_prefix(api_key.prefix)
87+
expected_last4 = random_part.last(4)
88+
89+
assert_equal expected_last4, api_key.last4
90+
assert_equal "#{api_key.prefix}••••#{api_key.last4}", api_key.masked_token
91+
end
6292
end
6393

6494
test ".active scope works" do

test/services/authenticator_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,45 @@ def mock_cache(read_map = {})
6060
assert_nil result.error_code
6161
end
6262

63+
test "authenticates successfully with bcrypt strategy" do
64+
with_hash_strategy(:bcrypt) do
65+
key = ApiKeys::ApiKey.create!(owner: @user, name: "BCrypt Auth Key")
66+
token = key.token
67+
key = ApiKeys::ApiKey.find(key.id)
68+
69+
request = mock_request(headers: { "Authorization" => "Bearer #{token}" })
70+
mock_cache
71+
72+
result = ApiKeys::Services::Authenticator.call(request)
73+
assert result.success?
74+
assert_equal key, result.api_key
75+
end
76+
end
77+
78+
test "authenticates with bcrypt when configured prefix mismatch triggers known prefixes scan" do
79+
with_hash_strategy(:bcrypt) do
80+
original_prefix = ApiKeys.configuration.token_prefix
81+
begin
82+
ApiKeys.configuration.token_prefix = -> { "pfx1_" }
83+
key = ApiKeys::ApiKey.create!(owner: @user, name: "BCrypt Prefix Key")
84+
token = key.token
85+
key = ApiKeys::ApiKey.find(key.id)
86+
87+
# Change configured prefix to force known-prefix path
88+
ApiKeys.configuration.token_prefix = -> { "otherpfx_" }
89+
90+
request = mock_request(headers: { "Authorization" => "Bearer #{token}" })
91+
mock_cache
92+
93+
result = ApiKeys::Services::Authenticator.call(request)
94+
assert result.success?
95+
assert_equal key, result.api_key
96+
ensure
97+
ApiKeys.configuration.token_prefix = original_prefix
98+
end
99+
end
100+
end
101+
63102
test "authenticates successfully with valid token in custom header" do
64103
ApiKeys.configure { |config| config.header = "X-Api-Key" }
65104
request = mock_request(headers: { "X-Api-Key" => @token })

test/test_helper.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ def teardown
9797
ApiKeys::ApiKey.delete_all
9898
User.delete_all
9999
end
100+
101+
# Temporarily set the configured hash strategy for the duration of the block
102+
# Ensures the original strategy is restored afterwards
103+
def with_hash_strategy(strategy)
104+
original = ApiKeys.configuration.hash_strategy
105+
ApiKeys.configuration.hash_strategy = strategy
106+
yield
107+
ensure
108+
ApiKeys.configuration.hash_strategy = original
109+
end
100110
end
101111

102112
puts "Test helper setup complete."

0 commit comments

Comments
 (0)