castle-rb 4.1.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +158 -43
  3. data/lib/castle.rb +46 -21
  4. data/lib/castle/api.rb +24 -12
  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 +52 -45
  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 +45 -28
  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/headers/extract.rb +47 -0
  41. data/lib/castle/headers/filter.rb +37 -0
  42. data/lib/castle/headers/format.rb +24 -0
  43. data/lib/castle/ip/extract.rb +83 -0
  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 +41 -23
  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} +9 -34
  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/core/get_connection_spec.rb +59 -0
  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/core/send_request_spec.rb +102 -0
  95. data/spec/lib/castle/failover/strategy_spec.rb +12 -0
  96. data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +18 -18
  97. data/spec/lib/castle/{headers_filter_spec.rb → headers/filter_spec.rb} +6 -5
  98. data/spec/lib/castle/headers/format_spec.rb +25 -0
  99. data/spec/lib/castle/{extractors/ip_spec.rb → ip/extract_spec.rb} +35 -7
  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/session_spec.rb +88 -0
  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 +133 -56
  114. data/lib/castle/api/request.rb +0 -42
  115. data/lib/castle/api/session.rb +0 -39
  116. data/lib/castle/commands/impersonate.rb +0 -26
  117. data/lib/castle/extractors/headers.rb +0 -45
  118. data/lib/castle/extractors/ip.rb +0 -68
  119. data/lib/castle/failover_auth_response.rb +0 -21
  120. data/lib/castle/headers_filter.rb +0 -35
  121. data/lib/castle/headers_formatter.rb +0 -22
  122. data/lib/castle/review.rb +0 -11
  123. data/lib/castle/utils.rb +0 -55
  124. data/lib/castle/utils/cloner.rb +0 -11
  125. data/lib/castle/utils/timestamp.rb +0 -12
  126. data/spec/lib/castle/api/request_spec.rb +0 -72
  127. data/spec/lib/castle/headers_formatter_spec.rb +0 -25
  128. data/spec/lib/castle/utils_spec.rb +0 -156
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Failover::Strategy do
4
+ subject(:strategy) { described_class }
5
+
6
+ it { expect(strategy::ALLOW).to be_eql(:allow) }
7
+ it { expect(strategy::DENY).to be_eql(:deny) }
8
+ it { expect(strategy::CHALLENGE).to be_eql(:challenge) }
9
+ it { expect(strategy::THROW).to be_eql(:throw) }
10
+
11
+ it { expect(Castle::Failover::STRATEGIES).to be_eql(%i[allow deny challenge throw]) }
12
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Castle::Extractors::Headers do
4
- subject(:headers) { described_class.new(formatted_headers).call }
3
+ describe Castle::Headers::Extract do
4
+ subject(:extract_call) { described_class.new(formatted_headers).call }
5
5
 
6
6
  let(:formatted_headers) do
7
7
  {
@@ -16,11 +16,11 @@ describe Castle::Extractors::Headers do
16
16
  end
17
17
 
18
18
  after do
19
- Castle.config.whitelisted = %w[]
20
- Castle.config.blacklisted = %w[]
19
+ Castle.config.allowlisted = %w[]
20
+ Castle.config.denylisted = %w[]
21
21
  end
22
22
 
23
- context 'when whitelist is not set in the configuration' do
23
+ context 'when allowlist is not set in the configuration' do
24
24
  let(:result) do
25
25
  {
26
26
  'Accept' => 'application/json',
@@ -33,11 +33,11 @@ describe Castle::Extractors::Headers do
33
33
  }
34
34
  end
35
35
 
36
- it { expect(headers).to eq(result) }
36
+ it { expect(extract_call).to eq(result) }
37
37
  end
38
38
 
39
- context 'when whitelist is set in the configuration' do
40
- before { Castle.config.whitelisted = %w[Accept OK] }
39
+ context 'when allowlist is set in the configuration' do
40
+ before { Castle.config.allowlisted = %w[Accept OK] }
41
41
 
42
42
  let(:result) do
43
43
  {
@@ -51,10 +51,10 @@ describe Castle::Extractors::Headers do
51
51
  }
52
52
  end
53
53
 
54
- it { expect(headers).to eq(result) }
54
+ it { expect(extract_call).to eq(result) }
55
55
  end
56
56
 
57
- context 'when blacklist is set in the configuration' do
57
+ context 'when denylist is set in the configuration' do
58
58
  context 'with a User-Agent' do
59
59
  let(:result) do
60
60
  {
@@ -68,9 +68,9 @@ describe Castle::Extractors::Headers do
68
68
  }
69
69
  end
70
70
 
71
- before { Castle.config.blacklisted = %w[User-Agent] }
71
+ before { Castle.config.denylisted = %w[User-Agent] }
72
72
 
73
- it { expect(headers).to eq(result) }
73
+ it { expect(extract_call).to eq(result) }
74
74
  end
75
75
 
76
76
  context 'with a different header' do
@@ -86,20 +86,20 @@ describe Castle::Extractors::Headers do
86
86
  }
87
87
  end
88
88
 
89
- before { Castle.config.blacklisted = %w[Accept] }
89
+ before { Castle.config.denylisted = %w[Accept] }
90
90
 
91
- it { expect(headers).to eq(result) }
91
+ it { expect(extract_call).to eq(result) }
92
92
  end
93
93
  end
94
94
 
95
- context 'when a header is both whitelisted and blacklisted' do
95
+ context 'when a header is both allowlisted and denylisted' do
96
96
  before do
97
- Castle.config.whitelisted = %w[Accept]
98
- Castle.config.blacklisted = %w[Accept]
97
+ Castle.config.allowlisted = %w[Accept]
98
+ Castle.config.denylisted = %w[Accept]
99
99
  end
100
100
 
101
101
  it do
102
- expect(headers['Accept']).to eq(true)
102
+ expect(extract_call['Accept']).to eq(true)
103
103
  end
104
104
  end
105
105
  end
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Castle::HeadersFilter do
4
- subject(:headers) { described_class.new(request).call }
3
+ describe Castle::Headers::Filter do
4
+ subject(:filter_call) { described_class.new(request).call }
5
5
 
6
6
  let(:env) do
7
- Rack::MockRequest.env_for(
7
+ result = Rack::MockRequest.env_for(
8
8
  '/',
9
9
  'Action-Dispatch.request.content-Type' => 'application/json',
10
10
  'HTTP_AUTHORIZATION' => 'Basic 123456',
11
11
  'HTTP_COOKIE' => '__cid=abcd;other=efgh',
12
- 'http-ok' => 'OK',
13
12
  'HTTP_ACCEPT' => 'application/json',
14
13
  'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
15
14
  'HTTP_USER_AGENT' => 'Mozilla 1234',
16
15
  'TEST' => '1',
17
16
  'REMOTE_ADDR' => '1.2.3.4'
18
17
  )
18
+ result[:HTTP_OK] = 'OK'
19
+ result
19
20
  end
20
21
  let(:filtered) do
21
22
  {
@@ -32,6 +33,6 @@ describe Castle::HeadersFilter do
32
33
  let(:request) { Rack::Request.new(env) }
33
34
 
34
35
  context 'with list of header' do
35
- it { expect(headers).to eq(filtered) }
36
+ it { expect(filter_call).to eq(filtered) }
36
37
  end
37
38
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Headers::Format do
4
+ subject(:format) { described_class }
5
+
6
+ it 'removes HTTP_' do
7
+ expect(format.call('HTTP_X_TEST')).to be_eql('X-Test')
8
+ end
9
+
10
+ it 'capitalizes header' do
11
+ expect(format.call('X_TEST')).to be_eql('X-Test')
12
+ end
13
+
14
+ it 'ignores letter case and -_ divider' do
15
+ expect(format.call('http-X_teST')).to be_eql('X-Test')
16
+ end
17
+
18
+ it 'does not remove http if there is no _- char' do
19
+ expect(format.call('httpX_teST')).to be_eql('Httpx-Test')
20
+ end
21
+
22
+ it 'capitalizes' do
23
+ expect(format.call(:clearance)).to be_eql('Clearance')
24
+ end
25
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Castle::Extractors::IP do
3
+ describe Castle::IP::Extract do
4
4
  subject(:extractor) { described_class.new(headers) }
5
5
 
6
6
  describe 'ip' do
@@ -21,20 +21,20 @@ describe Castle::Extractors::IP do
21
21
  end
22
22
 
23
23
  context 'with uppercase format' do
24
- before { Castle.config.ip_headers = %w[CF_CONNECTING_IP] }
24
+ before { Castle.config.ip_headers = %w[CF_CONNECTING_IP X-Forwarded-For] }
25
25
 
26
26
  it { expect(extractor.call).to eql('1.2.3.4') }
27
27
  end
28
28
 
29
29
  context 'with regular format' do
30
- before { Castle.config.ip_headers = %w[Cf-Connecting-Ip] }
30
+ before { Castle.config.ip_headers = %w[Cf-Connecting-Ip X-Forwarded-For] }
31
31
 
32
32
  it { expect(extractor.call).to eql('1.2.3.4') }
33
33
  end
34
34
 
35
- context 'with value from trusted proxies' do
35
+ context 'with value from trusted proxies it get seconds header' do
36
36
  before do
37
- Castle.config.ip_headers = %w[Cf-Connecting-Ip]
37
+ Castle.config.ip_headers = %w[Cf-Connecting-Ip X-Forwarded-For]
38
38
  Castle.config.trusted_proxies = %w[1.2.3.4]
39
39
  end
40
40
 
@@ -44,16 +44,44 @@ describe Castle::Extractors::IP do
44
44
 
45
45
  context 'with all the trusted proxies' do
46
46
  let(:http_x_header) do
47
- '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1,::1,fd00::,localhost,unix,unix:/tmp/sock'
47
+ '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1'
48
48
  end
49
49
 
50
50
  let(:headers) { { 'Remote-Addr' => '127.0.0.1', 'X-Forwarded-For' => http_x_header } }
51
51
 
52
- it 'fallbacks to remote_addr even if trusted proxy' do
52
+ it 'fallbacks to first available header when all headers are marked trusted proxy' do
53
53
  expect(extractor.call).to eql('127.0.0.1')
54
54
  end
55
55
  end
56
56
 
57
+ context 'with trust_proxy_chain option' do
58
+ let(:http_x_header) do
59
+ '6.6.6.6, 2.2.2.3, 6.6.6.5'
60
+ end
61
+
62
+ let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
63
+
64
+ before { Castle.config.trust_proxy_chain = true }
65
+
66
+ it 'selects first available header' do
67
+ expect(extractor.call).to eql('6.6.6.6')
68
+ end
69
+ end
70
+
71
+ context 'with trusted_proxy_depth option' do
72
+ let(:http_x_header) do
73
+ '6.6.6.6, 2.2.2.3, 6.6.6.5'
74
+ end
75
+
76
+ let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
77
+
78
+ before { Castle.config.trusted_proxy_depth = 1 }
79
+
80
+ it 'selects first available header' do
81
+ expect(extractor.call).to eql('2.2.2.3')
82
+ end
83
+ end
84
+
57
85
  context 'when list of not trusted ips provided in X_FORWARDED_FOR' do
58
86
  let(:headers) do
59
87
  {
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # tmp logger for testing
4
+ class TmpLogger
5
+ # @param _message [String]
6
+ def info(_message); end
7
+ end
8
+
9
+ describe Castle::Logger do
10
+ subject(:log) do
11
+ described_class.call(message, data)
12
+ end
13
+
14
+ let(:message) { 'https://localhost/test:' }
15
+ let(:integration_logger) { TmpLogger.new }
16
+ let(:data) { { a: 1 }.to_json }
17
+ let(:logger_message) { "[CASTLE] #{message} #{data}" }
18
+
19
+ before do
20
+ allow(integration_logger).to receive(:info).and_call_original
21
+ end
22
+
23
+ describe '.call' do
24
+ context 'without logger' do
25
+ before do
26
+ Castle.config.logger = nil
27
+ log
28
+ end
29
+
30
+ it { expect(integration_logger).not_to have_received(:info) }
31
+ end
32
+
33
+ context 'with logger' do
34
+ before do
35
+ Castle.config.logger = integration_logger
36
+ log
37
+ end
38
+
39
+ it { expect(integration_logger).to have_received(:info).with(logger_message) }
40
+ end
41
+ end
42
+ end
@@ -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
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Session do
4
+ describe '.call' do
5
+ context 'when ssl false' do
6
+ let(:localhost) { 'localhost' }
7
+ let(:port) { 3002 }
8
+
9
+ before do
10
+ Castle.config.base_url = 'http://localhost:3002'
11
+ stub_request(:get, 'localhost:3002/test').to_return(status: 200, body: '{}', headers: {})
12
+ end
13
+
14
+ context 'with block' do
15
+ let(:api_url) { '/test' }
16
+ let(:request) { Net::HTTP::Get.new(api_url) }
17
+
18
+ before do
19
+ allow(Net::HTTP)
20
+ .to receive(:new)
21
+ .with(localhost, port)
22
+ .and_call_original
23
+
24
+ described_class.call do |http|
25
+ http.request(request)
26
+ end
27
+ end
28
+
29
+ it do
30
+ expect(Net::HTTP)
31
+ .to have_received(:new)
32
+ .with(localhost, port)
33
+ end
34
+
35
+ it do
36
+ expect(a_request(:get, 'localhost:3002/test'))
37
+ .to have_been_made.once
38
+ end
39
+ end
40
+
41
+ context 'without block' do
42
+ before { described_class.call }
43
+
44
+ it do
45
+ expect(a_request(:get, 'localhost:3002/test'))
46
+ .not_to have_been_made
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'when ssl true' do
52
+ let(:localhost) { 'localhost' }
53
+ let(:port) { 443 }
54
+
55
+ before do
56
+ Castle.config.base_url = 'https://localhost'
57
+ stub_request(:get, 'https://localhost/test').to_return(
58
+ status: 200, body: '{}', headers: {}
59
+ )
60
+ end
61
+
62
+ context 'with block' do
63
+ let(:api_url) { '/test' }
64
+ let(:request) { Net::HTTP::Get.new(api_url) }
65
+
66
+ before do
67
+ allow(Net::HTTP)
68
+ .to receive(:new)
69
+ .with(localhost, port)
70
+ .and_call_original
71
+
72
+ allow(Net::HTTP)
73
+ .to receive(:start)
74
+
75
+ described_class.call do |http|
76
+ http.request(request)
77
+ end
78
+ end
79
+
80
+ it do
81
+ expect(Net::HTTP)
82
+ .to have_received(:new)
83
+ .with(localhost, port)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -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