castle-rb 3.6.2

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +157 -0
  3. data/lib/castle-rb.rb +3 -0
  4. data/lib/castle.rb +62 -0
  5. data/lib/castle/api.rb +40 -0
  6. data/lib/castle/api/request.rb +37 -0
  7. data/lib/castle/api/request/build.rb +27 -0
  8. data/lib/castle/api/response.rb +40 -0
  9. data/lib/castle/client.rb +106 -0
  10. data/lib/castle/command.rb +5 -0
  11. data/lib/castle/commands/authenticate.rb +23 -0
  12. data/lib/castle/commands/identify.rb +23 -0
  13. data/lib/castle/commands/impersonate.rb +26 -0
  14. data/lib/castle/commands/review.rb +14 -0
  15. data/lib/castle/commands/track.rb +23 -0
  16. data/lib/castle/configuration.rb +80 -0
  17. data/lib/castle/context/default.rb +40 -0
  18. data/lib/castle/context/merger.rb +14 -0
  19. data/lib/castle/context/sanitizer.rb +23 -0
  20. data/lib/castle/errors.rb +41 -0
  21. data/lib/castle/extractors/client_id.rb +17 -0
  22. data/lib/castle/extractors/headers.rb +51 -0
  23. data/lib/castle/extractors/ip.rb +18 -0
  24. data/lib/castle/failover_auth_response.rb +21 -0
  25. data/lib/castle/header_formatter.rb +9 -0
  26. data/lib/castle/review.rb +11 -0
  27. data/lib/castle/secure_mode.rb +11 -0
  28. data/lib/castle/support/hanami.rb +19 -0
  29. data/lib/castle/support/padrino.rb +19 -0
  30. data/lib/castle/support/rails.rb +13 -0
  31. data/lib/castle/support/sinatra.rb +19 -0
  32. data/lib/castle/utils.rb +55 -0
  33. data/lib/castle/utils/cloner.rb +11 -0
  34. data/lib/castle/utils/merger.rb +23 -0
  35. data/lib/castle/utils/timestamp.rb +12 -0
  36. data/lib/castle/validators/not_supported.rb +16 -0
  37. data/lib/castle/validators/present.rb +16 -0
  38. data/lib/castle/version.rb +5 -0
  39. data/spec/lib/castle/api/request/build_spec.rb +44 -0
  40. data/spec/lib/castle/api/request_spec.rb +59 -0
  41. data/spec/lib/castle/api/response_spec.rb +58 -0
  42. data/spec/lib/castle/api_spec.rb +37 -0
  43. data/spec/lib/castle/client_spec.rb +358 -0
  44. data/spec/lib/castle/command_spec.rb +9 -0
  45. data/spec/lib/castle/commands/authenticate_spec.rb +108 -0
  46. data/spec/lib/castle/commands/identify_spec.rb +87 -0
  47. data/spec/lib/castle/commands/impersonate_spec.rb +106 -0
  48. data/spec/lib/castle/commands/review_spec.rb +24 -0
  49. data/spec/lib/castle/commands/track_spec.rb +113 -0
  50. data/spec/lib/castle/configuration_spec.rb +130 -0
  51. data/spec/lib/castle/context/default_spec.rb +41 -0
  52. data/spec/lib/castle/context/merger_spec.rb +23 -0
  53. data/spec/lib/castle/context/sanitizer_spec.rb +27 -0
  54. data/spec/lib/castle/extractors/client_id_spec.rb +62 -0
  55. data/spec/lib/castle/extractors/headers_spec.rb +89 -0
  56. data/spec/lib/castle/extractors/ip_spec.rb +27 -0
  57. data/spec/lib/castle/header_formatter_spec.rb +25 -0
  58. data/spec/lib/castle/review_spec.rb +19 -0
  59. data/spec/lib/castle/secure_mode_spec.rb +9 -0
  60. data/spec/lib/castle/utils/cloner_spec.rb +18 -0
  61. data/spec/lib/castle/utils/merger_spec.rb +13 -0
  62. data/spec/lib/castle/utils/timestamp_spec.rb +17 -0
  63. data/spec/lib/castle/utils_spec.rb +156 -0
  64. data/spec/lib/castle/validators/not_supported_spec.rb +26 -0
  65. data/spec/lib/castle/validators/present_spec.rb +33 -0
  66. data/spec/lib/castle/version_spec.rb +5 -0
  67. data/spec/lib/castle_spec.rb +66 -0
  68. data/spec/spec_helper.rb +25 -0
  69. metadata +139 -0
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Commands::Identify do
4
+ subject(:instance) { described_class.new(context) }
5
+
6
+ let(:context) { { test: { test1: '1' } } }
7
+ let(:default_payload) { { user_id: '1234', sent_at: time_auto } }
8
+
9
+ let(:time_now) { Time.now }
10
+ let(:time_auto) { time_now.utc.iso8601(3) }
11
+
12
+ before { Timecop.freeze(time_now) }
13
+ after { Timecop.return }
14
+
15
+ describe '.build' do
16
+ subject(:command) { instance.build(payload) }
17
+
18
+ context 'with simple merger' do
19
+ let(:payload) { default_payload.merge(context: { test: { test2: '1' } }) }
20
+ let(:command_data) do
21
+ default_payload.merge(context: { test: { test1: '1', test2: '1' } })
22
+ end
23
+
24
+ it { expect(command.method).to be_eql(:post) }
25
+ it { expect(command.path).to be_eql('identify') }
26
+ it { expect(command.data).to be_eql(command_data) }
27
+ end
28
+
29
+ context 'with user_traits' do
30
+ let(:payload) { default_payload.merge(user_traits: { test: '1' }) }
31
+ let(:command_data) do
32
+ default_payload.merge(user_traits: { test: '1' }, context: context)
33
+ end
34
+
35
+ it { expect(command.method).to be_eql(:post) }
36
+ it { expect(command.path).to be_eql('identify') }
37
+ it { expect(command.data).to be_eql(command_data) }
38
+ end
39
+
40
+ context 'when active true' do
41
+ let(:payload) { default_payload.merge(context: { active: true }) }
42
+ let(:command_data) do
43
+ default_payload.merge(context: context.merge(active: true))
44
+ end
45
+
46
+ it { expect(command.method).to be_eql(:post) }
47
+ it { expect(command.path).to be_eql('identify') }
48
+ it { expect(command.data).to be_eql(command_data) }
49
+ end
50
+
51
+ context 'when active false' do
52
+ let(:payload) { default_payload.merge(context: { active: false }) }
53
+ let(:command_data) do
54
+ default_payload.merge(context: context.merge(active: false))
55
+ end
56
+
57
+ it { expect(command.method).to be_eql(:post) }
58
+ it { expect(command.path).to be_eql('identify') }
59
+ it { expect(command.data).to be_eql(command_data) }
60
+ end
61
+
62
+ context 'when active string' do
63
+ let(:payload) { default_payload.merge(context: { active: 'string' }) }
64
+ let(:command_data) { default_payload.merge(context: context) }
65
+
66
+ it { expect(command.method).to be_eql(:post) }
67
+ it { expect(command.path).to be_eql('identify') }
68
+ it { expect(command.data).to be_eql(command_data) }
69
+ end
70
+ end
71
+
72
+ describe '#validate!' do
73
+ subject(:validate!) { instance.build(payload) }
74
+
75
+ context 'with user_id not present' do
76
+ let(:payload) { {} }
77
+
78
+ it { expect { validate! }.not_to raise_error }
79
+ end
80
+
81
+ context 'with user_id present' do
82
+ let(:payload) { { user_id: '1234' } }
83
+
84
+ it { expect { validate! }.not_to raise_error }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Commands::Impersonate do
4
+ subject(:instance) { described_class.new(context) }
5
+
6
+ let(:context) { { user_agent: 'test', ip: '127.0.0.1', client_id: 'test' } }
7
+ let(:impersonator) { 'test@castle.io' }
8
+ let(:default_payload) { { user_id: '1234', sent_at: time_auto } }
9
+
10
+ let(:time_now) { Time.now }
11
+ let(:time_auto) { time_now.utc.iso8601(3) }
12
+
13
+ before { Timecop.freeze(time_now) }
14
+ after { Timecop.return }
15
+
16
+ describe '.build' do
17
+ subject(:command) { instance.build(payload) }
18
+
19
+ context 'with simple merger' do
20
+ let(:payload) { default_payload.merge(context: { test: { test2: '1' } }) }
21
+ let(:command_data) do
22
+ default_payload.merge(
23
+ context: {
24
+ test: { test2: '1' },
25
+ user_agent: 'test',
26
+ ip: '127.0.0.1',
27
+ client_id: 'test'
28
+ }
29
+ )
30
+ end
31
+
32
+ it { expect(command.method).to be_eql(:post) }
33
+ it { expect(command.path).to be_eql('impersonate') }
34
+ it { expect(command.data).to be_eql(command_data) }
35
+ end
36
+
37
+ context 'with impersonator' do
38
+ let(:payload) { default_payload.merge(impersonator: impersonator) }
39
+ let(:command_data) do
40
+ default_payload.merge(impersonator: impersonator, context: context)
41
+ end
42
+
43
+ it { expect(command.method).to be_eql(:post) }
44
+ it { expect(command.path).to be_eql('impersonate') }
45
+ it { expect(command.data).to be_eql(command_data) }
46
+ end
47
+
48
+ context 'when active true' do
49
+ let(:payload) { default_payload.merge(context: { active: true }) }
50
+ let(:command_data) do
51
+ default_payload.merge(context: context.merge(active: true))
52
+ end
53
+
54
+ it { expect(command.method).to be_eql(:post) }
55
+ it { expect(command.path).to be_eql('impersonate') }
56
+ it { expect(command.data).to be_eql(command_data) }
57
+ end
58
+
59
+ context 'when active false' do
60
+ let(:payload) { default_payload.merge(context: { active: false }) }
61
+ let(:command_data) do
62
+ default_payload.merge(context: context.merge(active: false))
63
+ end
64
+
65
+ it { expect(command.method).to be_eql(:post) }
66
+ it { expect(command.path).to be_eql('impersonate') }
67
+ it { expect(command.data).to be_eql(command_data) }
68
+ end
69
+
70
+ context 'when active string' do
71
+ let(:payload) { default_payload.merge(context: { active: 'string' }) }
72
+ let(:command_data) { default_payload.merge(context: context) }
73
+
74
+ it { expect(command.method).to be_eql(:post) }
75
+ it { expect(command.path).to be_eql('impersonate') }
76
+ it { expect(command.data).to be_eql(command_data) }
77
+ end
78
+
79
+ context 'when reset' do
80
+ let(:payload) { default_payload.merge(reset: true) }
81
+
82
+ it { expect(command.method).to be_eql(:delete) }
83
+ it { expect(command.path).to be_eql('impersonate') }
84
+ end
85
+ end
86
+
87
+ describe '#validate!' do
88
+ subject(:validate!) { instance.build(payload) }
89
+
90
+ context 'when user_id not present' do
91
+ let(:payload) { {} }
92
+
93
+ it do
94
+ expect do
95
+ validate!
96
+ end.to raise_error(Castle::InvalidParametersError, 'user_id is missing or empty')
97
+ end
98
+ end
99
+
100
+ context 'when user_id present' do
101
+ let(:payload) { { user_id: '1234' } }
102
+
103
+ it { expect { validate! }.not_to raise_error }
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Commands::Review do
4
+ subject(:instance) { described_class }
5
+
6
+ let(:context) { {} }
7
+ let(:review_id) { '1234' }
8
+
9
+ describe '.build' do
10
+ subject(:command) { instance.build(review_id) }
11
+
12
+ context 'without review_id' do
13
+ let(:review_id) { '' }
14
+
15
+ it { expect { command }.to raise_error(Castle::InvalidParametersError) }
16
+ end
17
+
18
+ context 'with review_id' do
19
+ it { expect(command.method).to be_eql(:get) }
20
+ it { expect(command.path).to be_eql("reviews/#{review_id}") }
21
+ it { expect(command.data).to be_nil }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Commands::Track do
4
+ subject(:instance) { described_class.new(context) }
5
+
6
+ let(:context) { { test: { test1: '1' } } }
7
+ let(:default_payload) { { event: '$login.track', sent_at: time_auto } }
8
+
9
+ let(:time_now) { Time.now }
10
+ let(:time_auto) { time_now.utc.iso8601(3) }
11
+
12
+ before { Timecop.freeze(time_now) }
13
+ after { Timecop.return }
14
+
15
+ describe '#build' do
16
+ subject(:command) { instance.build(payload) }
17
+
18
+ context 'with simple merger' do
19
+ let(:payload) { default_payload.merge(context: { test: { test2: '1' } }) }
20
+ let(:command_data) do
21
+ default_payload.merge(context: { test: { test1: '1', test2: '1' } })
22
+ end
23
+
24
+ it { expect(command.method).to be_eql(:post) }
25
+ it { expect(command.path).to be_eql('track') }
26
+ it { expect(command.data).to be_eql(command_data) }
27
+ end
28
+
29
+ context 'with user_id' do
30
+ let(:payload) { default_payload.merge(user_id: '1234') }
31
+ let(:command_data) do
32
+ default_payload.merge(user_id: '1234', context: context)
33
+ end
34
+
35
+ it { expect(command.method).to be_eql(:post) }
36
+ it { expect(command.path).to be_eql('track') }
37
+ it { expect(command.data).to be_eql(command_data) }
38
+ end
39
+
40
+ context 'with properties' do
41
+ let(:payload) { default_payload.merge(properties: { test: '1' }) }
42
+ let(:command_data) do
43
+ default_payload.merge(properties: { test: '1' }, context: context)
44
+ end
45
+
46
+ it { expect(command.method).to be_eql(:post) }
47
+ it { expect(command.path).to be_eql('track') }
48
+ it { expect(command.data).to be_eql(command_data) }
49
+ end
50
+
51
+ context 'with user_traits' do
52
+ let(:payload) { default_payload.merge(user_traits: { test: '1' }) }
53
+ let(:command_data) do
54
+ default_payload.merge(user_traits: { test: '1' }, context: context)
55
+ end
56
+
57
+ it { expect(command.method).to be_eql(:post) }
58
+ it { expect(command.path).to be_eql('track') }
59
+ it { expect(command.data).to be_eql(command_data) }
60
+ end
61
+
62
+ context 'when active true' do
63
+ let(:payload) { default_payload.merge(context: { active: true }) }
64
+ let(:command_data) do
65
+ default_payload.merge(context: context.merge(active: true))
66
+ end
67
+
68
+ it { expect(command.method).to be_eql(:post) }
69
+ it { expect(command.path).to be_eql('track') }
70
+ it { expect(command.data).to be_eql(command_data) }
71
+ end
72
+
73
+ context 'when active false' do
74
+ let(:payload) { default_payload.merge(context: { active: false }) }
75
+ let(:command_data) do
76
+ default_payload.merge(context: context.merge(active: false))
77
+ end
78
+
79
+ it { expect(command.method).to be_eql(:post) }
80
+ it { expect(command.path).to be_eql('track') }
81
+ it { expect(command.data).to be_eql(command_data) }
82
+ end
83
+
84
+ context 'when active string' do
85
+ let(:payload) { default_payload.merge(context: { active: 'string' }) }
86
+ let(:command_data) { default_payload.merge(context: context) }
87
+
88
+ it { expect(command.method).to be_eql(:post) }
89
+ it { expect(command.path).to be_eql('track') }
90
+ it { expect(command.data).to be_eql(command_data) }
91
+ end
92
+ end
93
+
94
+ describe '#validate!' do
95
+ subject(:validate!) { instance.build(payload) }
96
+
97
+ context 'when event not present' do
98
+ let(:payload) { {} }
99
+
100
+ it do
101
+ expect do
102
+ validate!
103
+ end.to raise_error(Castle::InvalidParametersError, 'event is missing or empty')
104
+ end
105
+ end
106
+
107
+ context 'when event present' do
108
+ let(:payload) { { event: '$login.track' } }
109
+
110
+ it { expect { validate! }.not_to raise_error }
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::Configuration do
4
+ subject(:config) do
5
+ described_class.new
6
+ end
7
+
8
+ describe 'host' do
9
+ context 'with default' do
10
+ it { expect(config.host).to be_eql('api.castle.io') }
11
+ end
12
+
13
+ context 'with setter' do
14
+ before { config.host = 'api.castle.dev' }
15
+
16
+ it { expect(config.host).to be_eql('api.castle.dev') }
17
+ end
18
+ end
19
+
20
+ describe 'post' do
21
+ context 'with default' do
22
+ it { expect(config.port).to be_eql(443) }
23
+ end
24
+
25
+ context 'with setter' do
26
+ before { config.port = 3001 }
27
+
28
+ it { expect(config.port).to be_eql(3001) }
29
+ end
30
+ end
31
+
32
+ describe 'api_secret' do
33
+ context 'with env' do
34
+ before do
35
+ allow(ENV).to receive(:fetch).with(
36
+ 'CASTLE_API_SECRET', ''
37
+ ).and_return('secret_key')
38
+ end
39
+
40
+ it do
41
+ expect(config.api_secret).to be_eql('secret_key')
42
+ end
43
+ end
44
+
45
+ context 'with setter' do
46
+ let(:value) { 'new_secret' }
47
+
48
+ before do
49
+ config.api_secret = value
50
+ end
51
+ it do
52
+ expect(config.api_secret).to be_eql(value)
53
+ end
54
+ end
55
+
56
+ it do
57
+ expect(config.api_secret).to be_eql('')
58
+ end
59
+ end
60
+
61
+ describe 'request_timeout' do
62
+ it do
63
+ expect(config.request_timeout).to be_eql(500)
64
+ end
65
+
66
+ context 'with setter' do
67
+ let(:value) { 50.0 }
68
+
69
+ before do
70
+ config.request_timeout = value
71
+ end
72
+ it do
73
+ expect(config.request_timeout).to be_eql(value)
74
+ end
75
+ end
76
+ end
77
+
78
+ describe 'whitelisted' do
79
+ it do
80
+ expect(config.whitelisted.size).to be_eql(0)
81
+ end
82
+
83
+ context 'with setter' do
84
+ before do
85
+ config.whitelisted = ['header']
86
+ end
87
+ it do
88
+ expect(config.whitelisted).to be_eql(['Header'])
89
+ end
90
+ end
91
+ end
92
+
93
+ describe 'blacklisted' do
94
+ it do
95
+ expect(config.blacklisted.size).to be_eql(0)
96
+ end
97
+
98
+ context 'with setter' do
99
+ before do
100
+ config.blacklisted = ['header']
101
+ end
102
+ it do
103
+ expect(config.blacklisted).to be_eql(['Header'])
104
+ end
105
+ end
106
+ end
107
+
108
+ describe 'failover_strategy' do
109
+ it do
110
+ expect(config.failover_strategy).to be_eql(:allow)
111
+ end
112
+
113
+ context 'with setter' do
114
+ before do
115
+ config.failover_strategy = :deny
116
+ end
117
+ it do
118
+ expect(config.failover_strategy).to be_eql(:deny)
119
+ end
120
+ end
121
+
122
+ context 'when broken' do
123
+ it do
124
+ expect do
125
+ config.failover_strategy = :unicorn
126
+ end.to raise_error(Castle::ConfigurationError)
127
+ end
128
+ end
129
+ end
130
+ end