castle-rb 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +2 -2
- data/lib/castle.rb +3 -1
- data/lib/castle/client.rb +17 -5
- data/lib/castle/commands/authenticate.rb +9 -3
- data/lib/castle/commands/identify.rb +9 -3
- data/lib/castle/commands/track.rb +9 -3
- data/lib/castle/context_merger.rb +5 -6
- data/lib/castle/context_sanitizer.rb +20 -0
- data/lib/castle/utils/merger.rb +8 -8
- data/lib/castle/utils/timestamp.rb +12 -0
- data/lib/castle/version.rb +1 -1
- data/spec/lib/castle/client_spec.rb +133 -7
- data/spec/lib/castle/commands/authenticate_spec.rb +12 -1
- data/spec/lib/castle/commands/identify_spec.rb +12 -1
- data/spec/lib/castle/commands/track_spec.rb +12 -1
- data/spec/lib/castle/context_merger_spec.rb +1 -12
- data/spec/lib/castle/context_sanitizer_spec.rb +27 -0
- data/spec/lib/castle/utils/timestamp_spec.rb +22 -0
- data/spec/spec_helper.rb +1 -0
- metadata +26 -21
- data/lib/castle/commands/with_context.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ef681fe4eea20aa8dacb69718809e49103e17060a5f2b0a57331982c9d36b59f
|
4
|
+
data.tar.gz: 6ab1e89bdefc563ce78ca7ae9666fb86f2b22ce0b740250a10f67fa3eaa1a98b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbd67d2101f93caa9c3c793171759bdf0401313ff6c3dbccacc0843674a6ec36d8b0c63e1c705a54e7f27d0b10b59990b85131ad6f492f02ba1e8b2673679583
|
7
|
+
data.tar.gz: c56cbdb356049465687277a388d39c62fe41a90e8b02436c38568fd7d8e18e7f8fcd12ad37e5f87c6b6b2a0fce0651da1a2863d3f14034b90da41cc5a876cf6e
|
data/README.md
CHANGED
@@ -122,7 +122,7 @@ end
|
|
122
122
|
|
123
123
|
```ruby
|
124
124
|
request_context = ::Castle::Client.to_context(request)
|
125
|
-
track_options = {
|
125
|
+
track_options = ::Castle::Client.to_options({
|
126
126
|
event: '$login.succeeded',
|
127
127
|
user_id: user.id,
|
128
128
|
properties: {
|
@@ -131,6 +131,6 @@ track_options = {
|
|
131
131
|
traits: {
|
132
132
|
key: 'value'
|
133
133
|
}
|
134
|
-
}
|
134
|
+
})
|
135
135
|
CastleTrackingWorker.perform_async(request_context, track_options)
|
136
136
|
```
|
data/lib/castle.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'net/http'
|
5
5
|
require 'json'
|
6
|
+
require 'time'
|
6
7
|
|
7
8
|
require 'castle/version'
|
8
9
|
require 'castle/errors'
|
@@ -10,9 +11,10 @@ require 'castle/command'
|
|
10
11
|
require 'castle/utils'
|
11
12
|
require 'castle/utils/merger'
|
12
13
|
require 'castle/utils/cloner'
|
14
|
+
require 'castle/utils/timestamp'
|
13
15
|
require 'castle/context_merger'
|
16
|
+
require 'castle/context_sanitizer'
|
14
17
|
require 'castle/default_context'
|
15
|
-
require 'castle/commands/with_context'
|
16
18
|
require 'castle/commands/identify'
|
17
19
|
require 'castle/commands/authenticate'
|
18
20
|
require 'castle/commands/track'
|
data/lib/castle/client.rb
CHANGED
@@ -4,19 +4,28 @@ module Castle
|
|
4
4
|
class Client
|
5
5
|
class << self
|
6
6
|
def from_request(request, options = {})
|
7
|
-
new(
|
7
|
+
new(
|
8
|
+
to_context(request, options),
|
9
|
+
to_options(options)
|
10
|
+
)
|
8
11
|
end
|
9
12
|
|
10
13
|
def to_context(request, options = {})
|
11
14
|
default_context = Castle::DefaultContext.new(request, options[:cookies]).call
|
12
|
-
Castle::ContextMerger.
|
15
|
+
Castle::ContextMerger.call(default_context, options[:context])
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_options(options = {})
|
19
|
+
options[:timestamp] ||= Castle::Utils::Timestamp.call
|
20
|
+
options
|
13
21
|
end
|
14
22
|
end
|
15
23
|
|
16
24
|
attr_accessor :api
|
17
25
|
|
18
|
-
def initialize(context,
|
19
|
-
@do_not_track = do_not_track
|
26
|
+
def initialize(context, options = {})
|
27
|
+
@do_not_track = options[:do_not_track]
|
28
|
+
@timestamp = options[:timestamp]
|
20
29
|
@context = context
|
21
30
|
@api = API.new
|
22
31
|
end
|
@@ -25,6 +34,7 @@ module Castle
|
|
25
34
|
options = Castle::Utils.deep_symbolize_keys(options || {})
|
26
35
|
|
27
36
|
if tracked?
|
37
|
+
options[:timestamp] ||= @timestamp if @timestamp
|
28
38
|
command = Castle::Commands::Authenticate.new(@context).build(options)
|
29
39
|
begin
|
30
40
|
@api.request(command).merge(failover: false, failover_reason: nil)
|
@@ -40,6 +50,7 @@ module Castle
|
|
40
50
|
options = Castle::Utils.deep_symbolize_keys(options || {})
|
41
51
|
|
42
52
|
return unless tracked?
|
53
|
+
options[:timestamp] ||= @timestamp if @timestamp
|
43
54
|
|
44
55
|
command = Castle::Commands::Identify.new(@context).build(options)
|
45
56
|
@api.request(command)
|
@@ -49,6 +60,7 @@ module Castle
|
|
49
60
|
options = Castle::Utils.deep_symbolize_keys(options || {})
|
50
61
|
|
51
62
|
return unless tracked?
|
63
|
+
options[:timestamp] ||= @timestamp if @timestamp
|
52
64
|
|
53
65
|
command = Castle::Commands::Track.new(@context).build(options)
|
54
66
|
@api.request(command)
|
@@ -70,7 +82,7 @@ module Castle
|
|
70
82
|
|
71
83
|
def setup_context(request, cookies, additional_context)
|
72
84
|
default_context = Castle::DefaultContext.new(request, cookies).call
|
73
|
-
Castle::ContextMerger.
|
85
|
+
Castle::ContextMerger.call(default_context, additional_context)
|
74
86
|
end
|
75
87
|
|
76
88
|
def failover_response_or_raise(failover_response, error)
|
@@ -3,13 +3,19 @@
|
|
3
3
|
module Castle
|
4
4
|
module Commands
|
5
5
|
class Authenticate
|
6
|
-
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
end
|
7
9
|
|
8
10
|
def build(options = {})
|
9
11
|
validate!(options)
|
10
|
-
|
12
|
+
context = ContextMerger.call(@context, options[:context])
|
13
|
+
context = ContextSanitizer.call(context)
|
11
14
|
|
12
|
-
Castle::Command.new('authenticate',
|
15
|
+
Castle::Command.new('authenticate',
|
16
|
+
options.merge(context: context,
|
17
|
+
sent_at: Castle::Utils::Timestamp.call),
|
18
|
+
:post)
|
13
19
|
end
|
14
20
|
|
15
21
|
private
|
@@ -3,13 +3,19 @@
|
|
3
3
|
module Castle
|
4
4
|
module Commands
|
5
5
|
class Identify
|
6
|
-
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
end
|
7
9
|
|
8
10
|
def build(options = {})
|
9
11
|
validate!(options)
|
10
|
-
|
12
|
+
context = ContextMerger.call(@context, options[:context])
|
13
|
+
context = ContextSanitizer.call(context)
|
11
14
|
|
12
|
-
Castle::Command.new('identify',
|
15
|
+
Castle::Command.new('identify',
|
16
|
+
options.merge(context: context,
|
17
|
+
sent_at: Castle::Utils::Timestamp.call),
|
18
|
+
:post)
|
13
19
|
end
|
14
20
|
|
15
21
|
private
|
@@ -3,13 +3,19 @@
|
|
3
3
|
module Castle
|
4
4
|
module Commands
|
5
5
|
class Track
|
6
|
-
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
end
|
7
9
|
|
8
10
|
def build(options = {})
|
9
11
|
validate!(options)
|
10
|
-
|
12
|
+
context = ContextMerger.call(@context, options[:context])
|
13
|
+
context = ContextSanitizer.call(context)
|
11
14
|
|
12
|
-
Castle::Command.new('track',
|
15
|
+
Castle::Command.new('track',
|
16
|
+
options.merge(context: context,
|
17
|
+
sent_at: Castle::Utils::Timestamp.call),
|
18
|
+
:post)
|
13
19
|
end
|
14
20
|
|
15
21
|
private
|
@@ -2,12 +2,11 @@
|
|
2
2
|
|
3
3
|
module Castle
|
4
4
|
class ContextMerger
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
Castle::Utils::Merger.call(@main_context, request_context)
|
5
|
+
class << self
|
6
|
+
def call(initial_context, request_context)
|
7
|
+
main_context = Castle::Utils::Cloner.call(initial_context)
|
8
|
+
Castle::Utils::Merger.call(main_context, request_context || {})
|
9
|
+
end
|
11
10
|
end
|
12
11
|
end
|
13
12
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
# removes not proper active flag values
|
5
|
+
class ContextSanitizer
|
6
|
+
class << self
|
7
|
+
def call(context)
|
8
|
+
sanitized_active_mode(context) || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def sanitized_active_mode(context)
|
14
|
+
return context unless context && context.key?(:active)
|
15
|
+
return context if [true, false].include?(context[:active])
|
16
|
+
context.reject { |key| key == :active }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/castle/utils/merger.rb
CHANGED
@@ -4,19 +4,19 @@ module Castle
|
|
4
4
|
module Utils
|
5
5
|
class Merger
|
6
6
|
def self.call(first, second)
|
7
|
-
Castle::Utils.deep_symbolize_keys
|
8
|
-
Castle::Utils.deep_symbolize_keys
|
7
|
+
first_s = Castle::Utils.deep_symbolize_keys(first)
|
8
|
+
second_s = Castle::Utils.deep_symbolize_keys(second)
|
9
9
|
|
10
|
-
|
10
|
+
second_s.each do |name, value|
|
11
11
|
if value.nil?
|
12
|
-
|
13
|
-
elsif value.is_a?(Hash) &&
|
14
|
-
call(
|
12
|
+
first_s.delete(name)
|
13
|
+
elsif value.is_a?(Hash) && first_s[name].is_a?(Hash)
|
14
|
+
first_s[name] = call(first_s[name], value)
|
15
15
|
else
|
16
|
-
|
16
|
+
first_s[name] = value
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
first_s
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/castle/version.rb
CHANGED
@@ -12,25 +12,40 @@ describe Castle::Client do
|
|
12
12
|
end
|
13
13
|
let(:request) { Rack::Request.new(env) }
|
14
14
|
let(:client) { described_class.from_request(request) }
|
15
|
+
let(:request_to_context) { described_class.to_context(request) }
|
16
|
+
let(:client_with_user_timestamp) do
|
17
|
+
described_class.new(request_to_context, timestamp: time_user)
|
18
|
+
end
|
19
|
+
let(:client_with_no_timestamp) { described_class.new(request_to_context) }
|
20
|
+
|
15
21
|
let(:headers) { { 'X-Forwarded-For' => ip.to_s } }
|
16
22
|
let(:context) do
|
17
23
|
{
|
18
24
|
client_id: 'abcd',
|
19
25
|
active: true,
|
20
26
|
origin: 'web',
|
21
|
-
headers:
|
27
|
+
headers: { 'X-Forwarded-For': ip.to_s },
|
22
28
|
ip: ip,
|
23
29
|
library: { name: 'castle-rb', version: '2.2.0' }
|
24
30
|
}
|
25
31
|
end
|
26
32
|
|
33
|
+
let(:time_now) { Time.now }
|
34
|
+
let(:time_auto) { time_now.utc.iso8601(3) }
|
35
|
+
let(:time_user) { (Time.now - 10_000).utc.iso8601(3) }
|
36
|
+
|
27
37
|
before do
|
38
|
+
Timecop.freeze(time_now)
|
28
39
|
stub_const('Castle::VERSION', '2.2.0')
|
29
40
|
stub_request(:any, /api.castle.io/).with(
|
30
41
|
basic_auth: ['', 'secret']
|
31
42
|
).to_return(status: 200, body: '{}', headers: {})
|
32
43
|
end
|
33
44
|
|
45
|
+
after do
|
46
|
+
Timecop.return
|
47
|
+
end
|
48
|
+
|
34
49
|
describe 'parses the request' do
|
35
50
|
before do
|
36
51
|
allow(Castle::API).to receive(:new).and_call_original
|
@@ -42,8 +57,26 @@ describe Castle::Client do
|
|
42
57
|
end
|
43
58
|
end
|
44
59
|
|
60
|
+
describe 'to_context' do
|
61
|
+
it do
|
62
|
+
expect(described_class.to_context(request)).to eql(context)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'to_options' do
|
67
|
+
let(:options) { { user_id: '1234', traits: { name: 'Jo' } } }
|
68
|
+
let(:result) { { user_id: '1234', traits: { name: 'Jo' }, timestamp: time_auto } }
|
69
|
+
|
70
|
+
it do
|
71
|
+
expect(described_class.to_options(options)).to eql(result)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
45
75
|
describe 'identify' do
|
46
|
-
let(:request_body)
|
76
|
+
let(:request_body) do
|
77
|
+
{ user_id: '1234', timestamp: time_auto,
|
78
|
+
sent_at: time_auto, context: context, traits: { name: 'Jo' } }
|
79
|
+
end
|
47
80
|
|
48
81
|
before { client.identify(options) }
|
49
82
|
|
@@ -55,6 +88,35 @@ describe Castle::Client do
|
|
55
88
|
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
56
89
|
end
|
57
90
|
end
|
91
|
+
|
92
|
+
context 'when passed timestamp in options and no defined timestamp' do
|
93
|
+
let(:client) { client_with_no_timestamp }
|
94
|
+
let(:options) { { user_id: '1234', traits: { name: 'Jo' }, timestamp: time_user } }
|
95
|
+
let(:request_body) do
|
96
|
+
{ user_id: '1234', traits: { name: 'Jo' }, context: context,
|
97
|
+
timestamp: time_user, sent_at: time_auto }
|
98
|
+
end
|
99
|
+
|
100
|
+
it do
|
101
|
+
assert_requested :post, 'https://api.castle.io/v1/identify', times: 1 do |req|
|
102
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'with client initialized with timestamp' do
|
108
|
+
let(:client) { client_with_user_timestamp }
|
109
|
+
let(:request_body) do
|
110
|
+
{ user_id: '1234', timestamp: time_user, sent_at: time_auto,
|
111
|
+
context: context, traits: { name: 'Jo' } }
|
112
|
+
end
|
113
|
+
|
114
|
+
it do
|
115
|
+
assert_requested :post, 'https://api.castle.io/v1/identify', times: 1 do |req|
|
116
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
58
120
|
end
|
59
121
|
|
60
122
|
context 'string keys' do
|
@@ -71,14 +133,46 @@ describe Castle::Client do
|
|
71
133
|
describe 'authenticate' do
|
72
134
|
let(:options) { { event: '$login.succeeded', user_id: '1234' } }
|
73
135
|
let(:request_response) { client.authenticate(options) }
|
74
|
-
let(:request_body)
|
136
|
+
let(:request_body) do
|
137
|
+
{ event: '$login.succeeded', user_id: '1234', context: context,
|
138
|
+
timestamp: time_auto, sent_at: time_auto }
|
139
|
+
end
|
75
140
|
|
76
141
|
context 'symbol keys' do
|
77
142
|
before { request_response }
|
78
143
|
|
79
144
|
it do
|
80
145
|
assert_requested :post, 'https://api.castle.io/v1/authenticate', times: 1 do |req|
|
81
|
-
req.body == request_body.to_json
|
146
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when passed timestamp in options and no defined timestamp' do
|
151
|
+
let(:client) { client_with_no_timestamp }
|
152
|
+
let(:options) { { event: '$login.succeeded', user_id: '1234', timestamp: time_user } }
|
153
|
+
let(:request_body) do
|
154
|
+
{ event: '$login.succeeded', user_id: '1234', context: context,
|
155
|
+
timestamp: time_user, sent_at: time_auto }
|
156
|
+
end
|
157
|
+
|
158
|
+
it do
|
159
|
+
assert_requested :post, 'https://api.castle.io/v1/authenticate', times: 1 do |req|
|
160
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'with client initialized with timestamp' do
|
166
|
+
let(:client) { client_with_user_timestamp }
|
167
|
+
let(:request_body) do
|
168
|
+
{ event: '$login.succeeded', user_id: '1234', context: context,
|
169
|
+
timestamp: time_user, sent_at: time_auto }
|
170
|
+
end
|
171
|
+
|
172
|
+
it do
|
173
|
+
assert_requested :post, 'https://api.castle.io/v1/authenticate', times: 1 do |req|
|
174
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
175
|
+
end
|
82
176
|
end
|
83
177
|
end
|
84
178
|
end
|
@@ -90,7 +184,7 @@ describe Castle::Client do
|
|
90
184
|
|
91
185
|
it do
|
92
186
|
assert_requested :post, 'https://api.castle.io/v1/authenticate', times: 1 do |req|
|
93
|
-
req.body == request_body.to_json
|
187
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
94
188
|
end
|
95
189
|
end
|
96
190
|
end
|
@@ -100,7 +194,7 @@ describe Castle::Client do
|
|
100
194
|
|
101
195
|
it do
|
102
196
|
assert_requested :post, 'https://api.castle.io/v1/authenticate', times: 1 do |req|
|
103
|
-
req.body == request_body.to_json
|
197
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
104
198
|
end
|
105
199
|
end
|
106
200
|
|
@@ -159,7 +253,10 @@ describe Castle::Client do
|
|
159
253
|
end
|
160
254
|
|
161
255
|
describe 'track' do
|
162
|
-
let(:request_body)
|
256
|
+
let(:request_body) do
|
257
|
+
{ event: '$login.succeeded', context: context, user_id: '1234',
|
258
|
+
timestamp: time_auto, sent_at: time_auto }
|
259
|
+
end
|
163
260
|
|
164
261
|
before { client.track(options) }
|
165
262
|
|
@@ -171,6 +268,35 @@ describe Castle::Client do
|
|
171
268
|
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
172
269
|
end
|
173
270
|
end
|
271
|
+
|
272
|
+
context 'when passed timestamp in options and no defined timestamp' do
|
273
|
+
let(:client) { client_with_no_timestamp }
|
274
|
+
let(:options) { { event: '$login.succeeded', user_id: '1234', timestamp: time_user } }
|
275
|
+
let(:request_body) do
|
276
|
+
{ event: '$login.succeeded', user_id: '1234', context: context,
|
277
|
+
timestamp: time_user, sent_at: time_auto }
|
278
|
+
end
|
279
|
+
|
280
|
+
it do
|
281
|
+
assert_requested :post, 'https://api.castle.io/v1/track', times: 1 do |req|
|
282
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context 'with client initialized with timestamp' do
|
288
|
+
let(:client) { client_with_user_timestamp }
|
289
|
+
let(:request_body) do
|
290
|
+
{ event: '$login.succeeded', context: context, user_id: '1234',
|
291
|
+
timestamp: time_user, sent_at: time_auto }
|
292
|
+
end
|
293
|
+
|
294
|
+
it do
|
295
|
+
assert_requested :post, 'https://api.castle.io/v1/track', times: 1 do |req|
|
296
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
174
300
|
end
|
175
301
|
|
176
302
|
context 'string keys' do
|
@@ -4,7 +4,18 @@ describe Castle::Commands::Authenticate do
|
|
4
4
|
subject(:instance) { described_class.new(context) }
|
5
5
|
|
6
6
|
let(:context) { { test: { test1: '1' } } }
|
7
|
-
let(:default_payload) { { event: '$login.authenticate', user_id: '1234' } }
|
7
|
+
let(:default_payload) { { event: '$login.authenticate', 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 do
|
13
|
+
Timecop.freeze(time_now)
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
Timecop.return
|
18
|
+
end
|
8
19
|
|
9
20
|
describe '.build' do
|
10
21
|
subject(:command) { instance.build(payload) }
|
@@ -4,7 +4,18 @@ describe Castle::Commands::Identify do
|
|
4
4
|
subject(:instance) { described_class.new(context) }
|
5
5
|
|
6
6
|
let(:context) { { test: { test1: '1' } } }
|
7
|
-
let(:default_payload) { { user_id: '1234' } }
|
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 do
|
13
|
+
Timecop.freeze(time_now)
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
Timecop.return
|
18
|
+
end
|
8
19
|
|
9
20
|
describe '.build' do
|
10
21
|
subject(:command) { instance.build(payload) }
|
@@ -4,7 +4,18 @@ describe Castle::Commands::Track do
|
|
4
4
|
subject(:instance) { described_class.new(context) }
|
5
5
|
|
6
6
|
let(:context) { { test: { test1: '1' } } }
|
7
|
-
let(:default_payload) { { event: '$login.track' } }
|
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 do
|
13
|
+
Timecop.freeze(time_now)
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
Timecop.return
|
18
|
+
end
|
8
19
|
|
9
20
|
describe '#build' do
|
10
21
|
subject(:command) { instance.build(payload) }
|
@@ -1,21 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe Castle::ContextMerger do
|
4
|
-
subject(:command) { described_class.new('go', { id: '1' }, :post) }
|
5
|
-
|
6
4
|
let(:first) { { test: { test1: { c: '4' } } } }
|
7
5
|
|
8
|
-
context '.new' do
|
9
|
-
subject(:instance) { described_class.new(first) }
|
10
|
-
|
11
|
-
it { expect(instance.instance_variable_get(:@main_context)).to eq(first) }
|
12
|
-
it do
|
13
|
-
expect(instance.instance_variable_get(:@main_context).object_id).not_to eq(first.object_id)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
6
|
context '#call' do
|
18
|
-
subject { described_class.
|
7
|
+
subject { described_class.call(first, second) }
|
19
8
|
|
20
9
|
let(:result) { { test: { test1: { c: '4', d: '5' } } } }
|
21
10
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Castle::ContextSanitizer do
|
4
|
+
let(:paylod) { { test: 'test' } }
|
5
|
+
|
6
|
+
describe '#call' do
|
7
|
+
subject { described_class.call(context) }
|
8
|
+
|
9
|
+
context 'when active true' do
|
10
|
+
let(:context) { paylod.merge(active: true) }
|
11
|
+
|
12
|
+
it { is_expected.to eql(context) }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when active false' do
|
16
|
+
let(:context) { paylod.merge(active: false) }
|
17
|
+
|
18
|
+
it { is_expected.to eql(context) }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when active string' do
|
22
|
+
let(:context) { paylod.merge(active: 'uknown') }
|
23
|
+
|
24
|
+
it { is_expected.to eql(paylod) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Castle::Utils::Timestamp do
|
4
|
+
subject { described_class.call }
|
5
|
+
|
6
|
+
let(:time_string) { '2018-01-10T14:14:24.407Z' }
|
7
|
+
let(:time) { Time.parse(time_string) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
Timecop.freeze(time)
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
Timecop.return
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#call' do
|
18
|
+
it do
|
19
|
+
is_expected.to eql(time_string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: castle-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Johan Brissmyr
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Castle protects your users from account compromise
|
14
14
|
email: johan@castle.io
|
@@ -26,9 +26,9 @@ files:
|
|
26
26
|
- lib/castle/commands/identify.rb
|
27
27
|
- lib/castle/commands/review.rb
|
28
28
|
- lib/castle/commands/track.rb
|
29
|
-
- lib/castle/commands/with_context.rb
|
30
29
|
- lib/castle/configuration.rb
|
31
30
|
- lib/castle/context_merger.rb
|
31
|
+
- lib/castle/context_sanitizer.rb
|
32
32
|
- lib/castle/default_context.rb
|
33
33
|
- lib/castle/errors.rb
|
34
34
|
- lib/castle/extractors/client_id.rb
|
@@ -47,6 +47,7 @@ files:
|
|
47
47
|
- lib/castle/utils.rb
|
48
48
|
- lib/castle/utils/cloner.rb
|
49
49
|
- lib/castle/utils/merger.rb
|
50
|
+
- lib/castle/utils/timestamp.rb
|
50
51
|
- lib/castle/version.rb
|
51
52
|
- spec/lib/castle/api_spec.rb
|
52
53
|
- spec/lib/castle/client_spec.rb
|
@@ -57,6 +58,7 @@ files:
|
|
57
58
|
- spec/lib/castle/commands/track_spec.rb
|
58
59
|
- spec/lib/castle/configuration_spec.rb
|
59
60
|
- spec/lib/castle/context_merger_spec.rb
|
61
|
+
- spec/lib/castle/context_sanitizer_spec.rb
|
60
62
|
- spec/lib/castle/default_context_spec.rb
|
61
63
|
- spec/lib/castle/extractors/client_id_spec.rb
|
62
64
|
- spec/lib/castle/extractors/headers_spec.rb
|
@@ -68,6 +70,7 @@ files:
|
|
68
70
|
- spec/lib/castle/secure_mode_spec.rb
|
69
71
|
- spec/lib/castle/utils/cloner_spec.rb
|
70
72
|
- spec/lib/castle/utils/merger_spec.rb
|
73
|
+
- spec/lib/castle/utils/timestamp_spec.rb
|
71
74
|
- spec/lib/castle/utils_spec.rb
|
72
75
|
- spec/lib/castle/version_spec.rb
|
73
76
|
- spec/lib/castle_spec.rb
|
@@ -92,32 +95,34 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
95
|
version: '0'
|
93
96
|
requirements: []
|
94
97
|
rubyforge_project:
|
95
|
-
rubygems_version: 2.
|
98
|
+
rubygems_version: 2.7.4
|
96
99
|
signing_key:
|
97
100
|
specification_version: 4
|
98
101
|
summary: Castle
|
99
102
|
test_files:
|
100
|
-
- spec/
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- spec/lib/castle_spec.rb
|
105
|
+
- spec/lib/castle/context_sanitizer_spec.rb
|
106
|
+
- spec/lib/castle/review_spec.rb
|
101
107
|
- spec/lib/castle/client_spec.rb
|
102
|
-
- spec/lib/castle/
|
103
|
-
- spec/lib/castle/commands/authenticate_spec.rb
|
104
|
-
- spec/lib/castle/commands/identify_spec.rb
|
105
|
-
- spec/lib/castle/commands/review_spec.rb
|
106
|
-
- spec/lib/castle/commands/track_spec.rb
|
108
|
+
- spec/lib/castle/api_spec.rb
|
107
109
|
- spec/lib/castle/configuration_spec.rb
|
108
|
-
- spec/lib/castle/
|
110
|
+
- spec/lib/castle/version_spec.rb
|
109
111
|
- spec/lib/castle/default_context_spec.rb
|
110
|
-
- spec/lib/castle/extractors/client_id_spec.rb
|
111
|
-
- spec/lib/castle/extractors/headers_spec.rb
|
112
|
-
- spec/lib/castle/extractors/ip_spec.rb
|
113
112
|
- spec/lib/castle/header_formatter_spec.rb
|
114
|
-
- spec/lib/castle/request_spec.rb
|
115
|
-
- spec/lib/castle/response_spec.rb
|
116
|
-
- spec/lib/castle/review_spec.rb
|
117
|
-
- spec/lib/castle/secure_mode_spec.rb
|
118
113
|
- spec/lib/castle/utils/cloner_spec.rb
|
114
|
+
- spec/lib/castle/utils/timestamp_spec.rb
|
119
115
|
- spec/lib/castle/utils/merger_spec.rb
|
116
|
+
- spec/lib/castle/command_spec.rb
|
117
|
+
- spec/lib/castle/request_spec.rb
|
118
|
+
- spec/lib/castle/response_spec.rb
|
119
|
+
- spec/lib/castle/commands/review_spec.rb
|
120
|
+
- spec/lib/castle/commands/authenticate_spec.rb
|
121
|
+
- spec/lib/castle/commands/track_spec.rb
|
122
|
+
- spec/lib/castle/commands/identify_spec.rb
|
123
|
+
- spec/lib/castle/context_merger_spec.rb
|
124
|
+
- spec/lib/castle/extractors/ip_spec.rb
|
125
|
+
- spec/lib/castle/extractors/headers_spec.rb
|
126
|
+
- spec/lib/castle/extractors/client_id_spec.rb
|
120
127
|
- spec/lib/castle/utils_spec.rb
|
121
|
-
- spec/lib/castle/
|
122
|
-
- spec/lib/castle_spec.rb
|
123
|
-
- spec/spec_helper.rb
|
128
|
+
- spec/lib/castle/secure_mode_spec.rb
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
module Commands
|
5
|
-
module WithContext
|
6
|
-
def initialize(context)
|
7
|
-
@context_merger = ContextMerger.new(context)
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def build_context!(options)
|
13
|
-
sanitize_active_mode!(options)
|
14
|
-
options[:context] = merge_context(options[:context])
|
15
|
-
end
|
16
|
-
|
17
|
-
def sanitize_active_mode!(options)
|
18
|
-
return unless options[:context] && options[:context].key?(:active)
|
19
|
-
return if [true, false].include?(options[:context][:active])
|
20
|
-
options[:context].delete(:active)
|
21
|
-
end
|
22
|
-
|
23
|
-
def merge_context(request_context)
|
24
|
-
@context_merger.call(request_context || {})
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|