castle-rb 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +107 -33
  3. data/lib/castle.rb +46 -22
  4. data/lib/castle/api.rb +22 -13
  5. data/lib/castle/api/approve_device.rb +25 -0
  6. data/lib/castle/api/authenticate.rb +34 -0
  7. data/lib/castle/api/end_impersonation.rb +29 -0
  8. data/lib/castle/api/get_device.rb +25 -0
  9. data/lib/castle/api/get_devices_for_user.rb +25 -0
  10. data/lib/castle/api/identify.rb +26 -0
  11. data/lib/castle/api/report_device.rb +25 -0
  12. data/lib/castle/api/review.rb +24 -0
  13. data/lib/castle/api/start_impersonation.rb +29 -0
  14. data/lib/castle/api/track.rb +26 -0
  15. data/lib/castle/client.rb +48 -62
  16. data/lib/castle/{extractors/client_id.rb → client_id/extract.rb} +2 -2
  17. data/lib/castle/commands/approve_device.rb +21 -0
  18. data/lib/castle/commands/authenticate.rb +13 -13
  19. data/lib/castle/commands/end_impersonation.rb +25 -0
  20. data/lib/castle/commands/get_device.rb +21 -0
  21. data/lib/castle/commands/get_devices_for_user.rb +21 -0
  22. data/lib/castle/commands/identify.rb +12 -13
  23. data/lib/castle/commands/report_device.rb +21 -0
  24. data/lib/castle/commands/review.rb +6 -3
  25. data/lib/castle/commands/start_impersonation.rb +25 -0
  26. data/lib/castle/commands/track.rb +12 -13
  27. data/lib/castle/configuration.rb +17 -19
  28. data/lib/castle/context/{default.rb → get_default.rb} +5 -6
  29. data/lib/castle/context/{merger.rb → merge.rb} +3 -3
  30. data/lib/castle/context/prepare.rb +18 -0
  31. data/lib/castle/context/{sanitizer.rb → sanitize.rb} +1 -1
  32. data/lib/castle/core/get_connection.rb +25 -0
  33. data/lib/castle/{api/response.rb → core/process_response.rb} +4 -2
  34. data/lib/castle/core/process_webhook.rb +20 -0
  35. data/lib/castle/core/send_request.rb +50 -0
  36. data/lib/castle/errors.rb +2 -0
  37. data/lib/castle/events.rb +1 -1
  38. data/lib/castle/failover/prepare_response.rb +23 -0
  39. data/lib/castle/failover/strategy.rb +20 -0
  40. data/lib/castle/{extractors/headers.rb → headers/extract.rb} +8 -6
  41. data/lib/castle/headers/filter.rb +37 -0
  42. data/lib/castle/headers/format.rb +24 -0
  43. data/lib/castle/{extractors/ip.rb → ip/extract.rb} +8 -7
  44. data/lib/castle/logger.rb +19 -0
  45. data/lib/castle/payload/prepare.rb +27 -0
  46. data/lib/castle/secure_mode.rb +6 -2
  47. data/lib/castle/session.rb +18 -0
  48. data/lib/castle/singleton_configuration.rb +9 -0
  49. data/lib/castle/utils/clean_invalid_chars.rb +24 -0
  50. data/lib/castle/utils/clone.rb +15 -0
  51. data/lib/castle/utils/deep_symbolize_keys.rb +45 -0
  52. data/lib/castle/utils/get_timestamp.rb +15 -0
  53. data/lib/castle/utils/{merger.rb → merge.rb} +3 -3
  54. data/lib/castle/utils/secure_compare.rb +22 -0
  55. data/lib/castle/validators/not_supported.rb +1 -0
  56. data/lib/castle/validators/present.rb +1 -0
  57. data/lib/castle/verdict.rb +13 -0
  58. data/lib/castle/version.rb +1 -1
  59. data/lib/castle/webhooks/verify.rb +43 -0
  60. data/spec/integration/rails/rails_spec.rb +33 -7
  61. data/spec/integration/rails/support/application.rb +3 -1
  62. data/spec/integration/rails/support/home_controller.rb +47 -5
  63. data/spec/lib/castle/api/approve_device_spec.rb +21 -0
  64. data/spec/lib/castle/api/authenticate_spec.rb +140 -0
  65. data/spec/lib/castle/api/end_impersonation_spec.rb +59 -0
  66. data/spec/lib/castle/api/get_device_spec.rb +19 -0
  67. data/spec/lib/castle/api/get_devices_for_user_spec.rb +19 -0
  68. data/spec/lib/castle/api/identify_spec.rb +68 -0
  69. data/spec/lib/castle/api/report_device_spec.rb +21 -0
  70. data/spec/lib/castle/{review_spec.rb → api/review_spec.rb} +3 -3
  71. data/spec/lib/castle/api/start_impersonation_spec.rb +59 -0
  72. data/spec/lib/castle/api/track_spec.rb +68 -0
  73. data/spec/lib/castle/api_spec.rb +16 -1
  74. data/spec/lib/castle/{extractors/client_id_spec.rb → client_id/extract_spec.rb} +2 -2
  75. data/spec/lib/castle/client_spec.rb +39 -21
  76. data/spec/lib/castle/commands/approve_device_spec.rb +24 -0
  77. data/spec/lib/castle/commands/authenticate_spec.rb +7 -16
  78. data/spec/lib/castle/commands/end_impersonation_spec.rb +82 -0
  79. data/spec/lib/castle/commands/get_device_spec.rb +24 -0
  80. data/spec/lib/castle/commands/get_devices_for_user_spec.rb +24 -0
  81. data/spec/lib/castle/commands/identify_spec.rb +5 -16
  82. data/spec/lib/castle/commands/report_device_spec.rb +24 -0
  83. data/spec/lib/castle/commands/review_spec.rb +1 -1
  84. data/spec/lib/castle/commands/{impersonate_spec.rb → start_impersonation_spec.rb} +7 -32
  85. data/spec/lib/castle/commands/track_spec.rb +5 -16
  86. data/spec/lib/castle/configuration_spec.rb +9 -138
  87. data/spec/lib/castle/context/{default_spec.rb → get_default_spec.rb} +1 -2
  88. data/spec/lib/castle/context/{merger_spec.rb → merge_spec.rb} +1 -1
  89. data/spec/lib/castle/context/prepare_spec.rb +44 -0
  90. data/spec/lib/castle/context/{sanitizer_spec.rb → sanitize_spec.rb} +1 -1
  91. data/spec/lib/castle/{api/connection_spec.rb → core/get_connection_spec.rb} +3 -3
  92. data/spec/lib/castle/{api/response_spec.rb → core/process_response_spec.rb} +56 -1
  93. data/spec/lib/castle/core/process_webhook_spec.rb +46 -0
  94. data/spec/lib/castle/{api/request_spec.rb → core/send_request_spec.rb} +20 -16
  95. data/spec/lib/castle/failover/strategy_spec.rb +12 -0
  96. data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +7 -7
  97. data/spec/lib/castle/{headers_filter_spec.rb → headers/filter_spec.rb} +3 -3
  98. data/spec/lib/castle/headers/format_spec.rb +25 -0
  99. data/spec/lib/castle/{extractors/ip_spec.rb → ip/extract_spec.rb} +1 -1
  100. data/spec/lib/castle/logger_spec.rb +42 -0
  101. data/spec/lib/castle/payload/prepare_spec.rb +54 -0
  102. data/spec/lib/castle/{api/session_spec.rb → session_spec.rb} +6 -4
  103. data/spec/lib/castle/singleton_configuration_spec.rb +18 -0
  104. data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +69 -0
  105. data/spec/lib/castle/utils/{cloner_spec.rb → clone_spec.rb} +3 -3
  106. data/spec/lib/castle/utils/deep_symbolize_keys_spec.rb +50 -0
  107. data/spec/lib/castle/utils/{timestamp_spec.rb → get_timestamp_spec.rb} +1 -1
  108. data/spec/lib/castle/utils/{merger_spec.rb → merge_spec.rb} +3 -3
  109. data/spec/lib/castle/verdict_spec.rb +9 -0
  110. data/spec/lib/castle/webhooks/verify_spec.rb +69 -0
  111. data/spec/spec_helper.rb +2 -0
  112. data/spec/support/shared_examples/configuration.rb +129 -0
  113. metadata +129 -57
  114. data/lib/castle/api/connection.rb +0 -24
  115. data/lib/castle/api/request.rb +0 -42
  116. data/lib/castle/api/session.rb +0 -20
  117. data/lib/castle/commands/impersonate.rb +0 -26
  118. data/lib/castle/failover_auth_response.rb +0 -21
  119. data/lib/castle/headers_filter.rb +0 -35
  120. data/lib/castle/headers_formatter.rb +0 -22
  121. data/lib/castle/review.rb +0 -11
  122. data/lib/castle/utils.rb +0 -55
  123. data/lib/castle/utils/cloner.rb +0 -11
  124. data/lib/castle/utils/timestamp.rb +0 -12
  125. data/spec/lib/castle/headers_formatter_spec.rb +0 -25
  126. data/spec/lib/castle/utils_spec.rb +0 -156
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Payload::Prepare do
4
+ let(:ip) { '1.2.3.4' }
5
+ let(:cookie_id) { 'abcd' }
6
+ let(:ua) { 'Chrome' }
7
+ let(:env) do
8
+ Rack::MockRequest.env_for(
9
+ '/',
10
+ 'HTTP_USER_AGENT' => ua,
11
+ 'HTTP_X_FORWARDED_FOR' => ip,
12
+ 'HTTP_COOKIE' => "__cid=#{cookie_id};other=efgh"
13
+ )
14
+ end
15
+ let(:request) { Rack::Request.new(env) }
16
+
17
+ let(:headers) do
18
+ {
19
+ 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true
20
+ }
21
+ end
22
+ let(:context) do
23
+ {
24
+ client_id: 'abcd',
25
+ active: true,
26
+ user_agent: ua,
27
+ headers: headers,
28
+ ip: ip,
29
+ library: { name: 'castle-rb', version: '2.2.0' }
30
+ }
31
+ end
32
+
33
+ let(:time_now) { Time.now }
34
+ let(:time_formatted) { time_now.utc.iso8601(3) }
35
+ let(:payload_options) { { user_id: '1234', user_traits: { name: 'Jo' } } }
36
+ let(:result) do
37
+ { user_id: '1234', user_traits: { name: 'Jo' }, timestamp: time_formatted, context: context }
38
+ end
39
+
40
+ before do
41
+ Timecop.freeze(time_now)
42
+ stub_const('Castle::VERSION', '2.2.0')
43
+ end
44
+
45
+ after { Timecop.return }
46
+
47
+ describe '#call' do
48
+ subject(:generated) { described_class.call(payload_options, request) }
49
+
50
+ context 'when active true' do
51
+ it { is_expected.to eql(result) }
52
+ end
53
+ end
54
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Castle::API::Session do
3
+ describe Castle::Session do
4
4
  describe '.call' do
5
5
  context 'when ssl false' do
6
6
  let(:localhost) { 'localhost' }
7
7
  let(:port) { 3002 }
8
8
 
9
9
  before do
10
- Castle.config.url = 'http://localhost:3002'
10
+ Castle.config.base_url = 'http://localhost:3002'
11
11
  stub_request(:get, 'localhost:3002/test').to_return(status: 200, body: '{}', headers: {})
12
12
  end
13
13
 
@@ -53,8 +53,10 @@ describe Castle::API::Session do
53
53
  let(:port) { 443 }
54
54
 
55
55
  before do
56
- Castle.config.url = 'https://localhost'
57
- stub_request(:get, 'https://localhost/test').to_return(status: 200, body: '{}', headers: {})
56
+ Castle.config.base_url = 'https://localhost'
57
+ stub_request(:get, 'https://localhost/test').to_return(
58
+ status: 200, body: '{}', headers: {}
59
+ )
58
60
  end
59
61
 
60
62
  context 'with block' do
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::SingletonConfiguration do
4
+ subject(:config) do
5
+ described_class.instance
6
+ end
7
+
8
+ it_behaves_like 'configuration_host'
9
+ it_behaves_like 'configuration_request_timeout'
10
+ it_behaves_like 'configuration_allowlisted'
11
+ it_behaves_like 'configuration_denylisted'
12
+ it_behaves_like 'configuration_failover_strategy'
13
+ it_behaves_like 'configuration_api_secret'
14
+
15
+ it do
16
+ expect(config.api_secret).to be_eql('secret')
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Utils::CleanInvalidChars do
4
+ describe '::call' do
5
+ subject { described_class.call(input) }
6
+
7
+ context 'when input is a string' do
8
+ let(:input) { '1234' }
9
+
10
+ it { is_expected.to eq input }
11
+ end
12
+
13
+ context 'when input is an array' do
14
+ let(:input) { [1, 2, 3, '4'] }
15
+
16
+ it { is_expected.to eq input }
17
+ end
18
+
19
+ context 'when input is a hash' do
20
+ let(:input) { { user_id: 1 } }
21
+
22
+ it { is_expected.to eq input }
23
+ end
24
+
25
+ context 'when input is nil' do
26
+ let(:input) { nil }
27
+
28
+ it { is_expected.to eq input }
29
+ end
30
+
31
+ context 'when input is a nested hash' do
32
+ let(:input) { { user: { id: 1 } } }
33
+
34
+ it { is_expected.to eq input }
35
+ end
36
+
37
+ context 'with invalid UTF-8 characters' do
38
+ context 'when input is a hash' do
39
+ let(:input) { { user_id: "inv\xC4lid" } }
40
+
41
+ it { is_expected.to eq(user_id: 'inv�lid') }
42
+ end
43
+
44
+ context 'when input is a nested hash' do
45
+ let(:input) { { user: { id: "inv\xC4lid" } } }
46
+
47
+ it { is_expected.to eq(user: { id: 'inv�lid' }) }
48
+ end
49
+
50
+ context 'when input is an array of hashes' do
51
+ let(:input) { [{ user: "inv\xC4lid" }] * 2 }
52
+
53
+ it { is_expected.to eq([{ user: 'inv�lid' }, { user: 'inv�lid' }]) }
54
+ end
55
+
56
+ context 'when input is an array' do
57
+ let(:input) { ["inv\xC4lid"] * 2 }
58
+
59
+ it { is_expected.to eq(['inv�lid', 'inv�lid']) }
60
+ end
61
+
62
+ context 'when input is a hash with array in key' do
63
+ let(:input) { { items: ["inv\xC4lid"] * 2 } }
64
+
65
+ it { is_expected.to eq(items: ['inv�lid', 'inv�lid']) }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Castle::Utils::Cloner do
4
- subject(:cloner) { described_class }
3
+ describe Castle::Utils::Clone do
4
+ subject(:clone) { described_class }
5
5
 
6
6
  describe 'call' do
7
7
  let(:nested) { { c: '3' } }
8
8
  let(:first) { { test: { test1: { c: '4' }, test2: nested, a: '1', b: '2' } } }
9
9
  let(:result) { { test: { test1: { c: '4' }, test2: { c: '3' }, a: '1', b: '2' } } }
10
- let(:cloned) { cloner.call(first) }
10
+ let(:cloned) { clone.call(first) }
11
11
 
12
12
  before { cloned }
13
13
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Utils::DeepSymbolizeKeys do
4
+ let(:nested_strings) { { 'a' => { 'b' => { 'c' => 3 } } } }
5
+ let(:nested_symbols) { { a: { b: { c: 3 } } } }
6
+ let(:nested_mixed) { { 'a' => { b: { 'c' => 3 } } } }
7
+ let(:string_array_of_hashes) { { 'a' => [{ 'b' => 2 }, { 'c' => 3 }, 4] } }
8
+ let(:symbol_array_of_hashes) { { a: [{ b: 2 }, { c: 3 }, 4] } }
9
+ let(:mixed_array_of_hashes) { { a: [{ b: 2 }, { 'c' => 3 }, 4] } }
10
+
11
+ describe '::call' do
12
+ subject { described_class.call(hash) }
13
+
14
+ context 'when nested_symbols' do
15
+ let(:hash) { nested_symbols }
16
+
17
+ it { is_expected.to eq(nested_symbols) }
18
+ end
19
+
20
+ context 'when nested_strings' do
21
+ let(:hash) { nested_strings }
22
+
23
+ it { is_expected.to eq(nested_symbols) }
24
+ end
25
+
26
+ context 'when nested_mixed' do
27
+ let(:hash) { nested_mixed }
28
+
29
+ it { is_expected.to eq(nested_symbols) }
30
+ end
31
+
32
+ context 'when string_array_of_hashes' do
33
+ let(:hash) { string_array_of_hashes }
34
+
35
+ it { is_expected.to eq(symbol_array_of_hashes) }
36
+ end
37
+
38
+ context 'when symbol_array_of_hashes' do
39
+ let(:hash) { symbol_array_of_hashes }
40
+
41
+ it { is_expected.to eq(symbol_array_of_hashes) }
42
+ end
43
+
44
+ context 'when mixed_array_of_hashes' do
45
+ let(:hash) { mixed_array_of_hashes }
46
+
47
+ it { is_expected.to eq(symbol_array_of_hashes) }
48
+ end
49
+ end
50
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Castle::Utils::Timestamp do
3
+ describe Castle::Utils::GetTimestamp do
4
4
  subject(:timestamp) { described_class.call }
5
5
 
6
6
  let(:time_string) { '2018-01-10T14:14:24.407Z' }
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Castle::Utils::Merger do
4
- subject(:merger) { described_class }
3
+ describe Castle::Utils::Merge do
4
+ subject(:merge) { described_class }
5
5
 
6
6
  describe 'call' do
7
7
  let(:first) { { test: { test1: { c: '4' }, test2: { c: '3' }, a: '1', b: '2' } } }
8
8
  let(:second) { { test2: '2', test: { 'test1' => { d: '5' }, test2: '6', a: nil, b: '3' } } }
9
9
  let(:result) { { test2: '2', test: { test1: { c: '4', d: '5' }, test2: '6', b: '3' } } }
10
10
 
11
- it { expect(merger.call(first, second)).to be_eql(result) }
11
+ it { expect(merge.call(first, second)).to be_eql(result) }
12
12
  end
13
13
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Verdict do
4
+ subject(:verdict) { described_class }
5
+
6
+ it { expect(verdict::ALLOW).to be_eql('allow') }
7
+ it { expect(verdict::DENY).to be_eql('deny') }
8
+ it { expect(verdict::CHALLENGE).to be_eql('challenge') }
9
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Webhooks::Verify do
4
+ describe '#call' do
5
+ subject(:call) { described_class.call(webhook) }
6
+
7
+ let(:env) do
8
+ Rack::MockRequest.env_for(
9
+ '/',
10
+ 'HTTP_X_CASTLE_SIGNATURE' => signature
11
+ )
12
+ end
13
+
14
+ let(:webhook) { Rack::Request.new(env) }
15
+ let(:user_id) { '12345' }
16
+ let(:webhook_body) do
17
+ {
18
+ api_version: 'v1',
19
+ app_id: '12345',
20
+ type: '$incident.confirmed',
21
+ created_at: '2020-12-18T12:55:21.779Z',
22
+ data: {
23
+ id: 'test',
24
+ device_token: 'token',
25
+ user_id: user_id,
26
+ trigger: '$login.succeeded',
27
+ context: {},
28
+ location: {},
29
+ user_agent: {}
30
+ },
31
+ user_traits: {},
32
+ properties: {},
33
+ policy: {}
34
+ }.to_json
35
+ end
36
+
37
+ context 'when success' do
38
+ let(:signature) { '3ptx3rUOBnGEqPjMrbcJn2UUfzwTKP54dFyP5uyPY+Y=' }
39
+
40
+ before do
41
+ allow(Castle::Core::ProcessWebhook)
42
+ .to receive(:call)
43
+ .and_return(webhook_body)
44
+ end
45
+
46
+ it do
47
+ expect { call }.not_to raise_error
48
+ end
49
+ end
50
+
51
+ context 'when signature is malformed' do
52
+ let(:signature) { '123' }
53
+
54
+ before do
55
+ allow(Castle::Core::ProcessWebhook)
56
+ .to receive(:call)
57
+ .and_return(webhook_body)
58
+ end
59
+
60
+ it do
61
+ expect { call }
62
+ .to raise_error(
63
+ Castle::WebhookVerificationError,
64
+ 'Signature not matching the expected signature'
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
@@ -14,6 +14,8 @@ require 'castle'
14
14
 
15
15
  WebMock.disable_net_connect!(allow_localhost: true)
16
16
 
17
+ Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
18
+
17
19
  RSpec.configure do |config|
18
20
  config.before do
19
21
  Castle.config.reset
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'configuration_host' do
4
+ describe 'host' do
5
+ context 'with default' do
6
+ it { expect(config.base_url.host).to be_eql('api.castle.io') }
7
+ end
8
+
9
+ context 'with setter' do
10
+ before { config.base_url = 'http://api.castle.dev/v2' }
11
+
12
+ it { expect(config.base_url.host).to be_eql('api.castle.dev') }
13
+ end
14
+ end
15
+ end
16
+
17
+ shared_examples 'configuration_request_timeout' do
18
+ describe 'request_timeout' do
19
+ it do
20
+ expect(config.request_timeout).to be_eql(1000)
21
+ end
22
+
23
+ context 'with setter' do
24
+ let(:value) { 50.0 }
25
+
26
+ before do
27
+ config.request_timeout = value
28
+ end
29
+
30
+ it do
31
+ expect(config.request_timeout).to be_eql(value)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ shared_examples 'configuration_allowlisted' do
38
+ describe 'allowlisted' do
39
+ it do
40
+ expect(config.allowlisted.size).to be_eql(0)
41
+ end
42
+
43
+ context 'with setter' do
44
+ before do
45
+ config.allowlisted = ['header']
46
+ end
47
+
48
+ it do
49
+ expect(config.allowlisted).to be_eql(['Header'])
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ shared_examples 'configuration_denylisted' do
56
+ describe 'denylisted' do
57
+ it do
58
+ expect(config.denylisted.size).to be_eql(0)
59
+ end
60
+
61
+ context 'with setter' do
62
+ before do
63
+ config.denylisted = ['header']
64
+ end
65
+
66
+ it do
67
+ expect(config.denylisted).to be_eql(['Header'])
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ shared_examples 'configuration_failover_strategy' do
74
+ describe 'failover_strategy' do
75
+ it do
76
+ expect(config.failover_strategy).to be_eql(Castle::Failover::Strategy::ALLOW)
77
+ end
78
+
79
+ context 'with setter' do
80
+ before do
81
+ config.failover_strategy = Castle::Failover::Strategy::DENY
82
+ end
83
+
84
+ it do
85
+ expect(config.failover_strategy).to be_eql(Castle::Failover::Strategy::DENY)
86
+ end
87
+ end
88
+
89
+ context 'when broken' do
90
+ it do
91
+ expect do
92
+ config.failover_strategy = :unicorn
93
+ end.to raise_error(Castle::ConfigurationError)
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ shared_examples 'configuration_api_secret' do
100
+ describe 'api_secret' do
101
+ context 'with env' do
102
+ let(:secret_key_env) { 'secret_key_env' }
103
+ let(:secret_key) { 'secret_key' }
104
+
105
+ before do
106
+ allow(ENV).to receive(:fetch).with(
107
+ 'CASTLE_API_SECRET', ''
108
+ ).and_return(secret_key_env)
109
+ config.reset
110
+ end
111
+
112
+ it { expect(config.api_secret).to be_eql(secret_key_env) }
113
+
114
+ context 'when key is overwritten' do
115
+ before { config.api_secret = secret_key }
116
+
117
+ it { expect(config.api_secret).to be_eql(secret_key) }
118
+ end
119
+ end
120
+
121
+ context 'with setter' do
122
+ let(:value) { 'new_secret' }
123
+
124
+ before { config.api_secret = value }
125
+
126
+ it { expect(config.api_secret).to be_eql(value) }
127
+ end
128
+ end
129
+ end