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.
- checksums.yaml +4 -4
- data/README.md +158 -43
- data/lib/castle.rb +46 -21
- data/lib/castle/api.rb +24 -12
- data/lib/castle/api/approve_device.rb +25 -0
- data/lib/castle/api/authenticate.rb +34 -0
- data/lib/castle/api/end_impersonation.rb +29 -0
- data/lib/castle/api/get_device.rb +25 -0
- data/lib/castle/api/get_devices_for_user.rb +25 -0
- data/lib/castle/api/identify.rb +26 -0
- data/lib/castle/api/report_device.rb +25 -0
- data/lib/castle/api/review.rb +24 -0
- data/lib/castle/api/start_impersonation.rb +29 -0
- data/lib/castle/api/track.rb +26 -0
- data/lib/castle/client.rb +52 -45
- data/lib/castle/{extractors/client_id.rb → client_id/extract.rb} +2 -2
- data/lib/castle/commands/approve_device.rb +21 -0
- data/lib/castle/commands/authenticate.rb +13 -13
- data/lib/castle/commands/end_impersonation.rb +25 -0
- data/lib/castle/commands/get_device.rb +21 -0
- data/lib/castle/commands/get_devices_for_user.rb +21 -0
- data/lib/castle/commands/identify.rb +12 -13
- data/lib/castle/commands/report_device.rb +21 -0
- data/lib/castle/commands/review.rb +6 -3
- data/lib/castle/commands/start_impersonation.rb +25 -0
- data/lib/castle/commands/track.rb +12 -13
- data/lib/castle/configuration.rb +45 -28
- data/lib/castle/context/{default.rb → get_default.rb} +5 -6
- data/lib/castle/context/{merger.rb → merge.rb} +3 -3
- data/lib/castle/context/prepare.rb +18 -0
- data/lib/castle/context/{sanitizer.rb → sanitize.rb} +1 -1
- data/lib/castle/core/get_connection.rb +25 -0
- data/lib/castle/{api/response.rb → core/process_response.rb} +4 -2
- data/lib/castle/core/process_webhook.rb +20 -0
- data/lib/castle/core/send_request.rb +50 -0
- data/lib/castle/errors.rb +2 -0
- data/lib/castle/events.rb +1 -1
- data/lib/castle/failover/prepare_response.rb +23 -0
- data/lib/castle/failover/strategy.rb +20 -0
- data/lib/castle/headers/extract.rb +47 -0
- data/lib/castle/headers/filter.rb +37 -0
- data/lib/castle/headers/format.rb +24 -0
- data/lib/castle/ip/extract.rb +83 -0
- data/lib/castle/logger.rb +19 -0
- data/lib/castle/payload/prepare.rb +27 -0
- data/lib/castle/secure_mode.rb +6 -2
- data/lib/castle/session.rb +18 -0
- data/lib/castle/singleton_configuration.rb +9 -0
- data/lib/castle/utils/clean_invalid_chars.rb +24 -0
- data/lib/castle/utils/clone.rb +15 -0
- data/lib/castle/utils/deep_symbolize_keys.rb +45 -0
- data/lib/castle/utils/get_timestamp.rb +15 -0
- data/lib/castle/utils/{merger.rb → merge.rb} +3 -3
- data/lib/castle/utils/secure_compare.rb +22 -0
- data/lib/castle/validators/not_supported.rb +1 -0
- data/lib/castle/validators/present.rb +1 -0
- data/lib/castle/verdict.rb +13 -0
- data/lib/castle/version.rb +1 -1
- data/lib/castle/webhooks/verify.rb +43 -0
- data/spec/integration/rails/rails_spec.rb +33 -7
- data/spec/integration/rails/support/application.rb +3 -1
- data/spec/integration/rails/support/home_controller.rb +47 -5
- data/spec/lib/castle/api/approve_device_spec.rb +21 -0
- data/spec/lib/castle/api/authenticate_spec.rb +140 -0
- data/spec/lib/castle/api/end_impersonation_spec.rb +59 -0
- data/spec/lib/castle/api/get_device_spec.rb +19 -0
- data/spec/lib/castle/api/get_devices_for_user_spec.rb +19 -0
- data/spec/lib/castle/api/identify_spec.rb +68 -0
- data/spec/lib/castle/api/report_device_spec.rb +21 -0
- data/spec/lib/castle/{review_spec.rb → api/review_spec.rb} +3 -3
- data/spec/lib/castle/api/start_impersonation_spec.rb +59 -0
- data/spec/lib/castle/api/track_spec.rb +68 -0
- data/spec/lib/castle/api_spec.rb +16 -1
- data/spec/lib/castle/{extractors/client_id_spec.rb → client_id/extract_spec.rb} +2 -2
- data/spec/lib/castle/client_spec.rb +41 -23
- data/spec/lib/castle/commands/approve_device_spec.rb +24 -0
- data/spec/lib/castle/commands/authenticate_spec.rb +7 -16
- data/spec/lib/castle/commands/end_impersonation_spec.rb +82 -0
- data/spec/lib/castle/commands/get_device_spec.rb +24 -0
- data/spec/lib/castle/commands/get_devices_for_user_spec.rb +24 -0
- data/spec/lib/castle/commands/identify_spec.rb +5 -16
- data/spec/lib/castle/commands/report_device_spec.rb +24 -0
- data/spec/lib/castle/commands/review_spec.rb +1 -1
- data/spec/lib/castle/commands/{impersonate_spec.rb → start_impersonation_spec.rb} +9 -34
- data/spec/lib/castle/commands/track_spec.rb +5 -16
- data/spec/lib/castle/configuration_spec.rb +9 -138
- data/spec/lib/castle/context/{default_spec.rb → get_default_spec.rb} +1 -2
- data/spec/lib/castle/context/{merger_spec.rb → merge_spec.rb} +1 -1
- data/spec/lib/castle/context/prepare_spec.rb +44 -0
- data/spec/lib/castle/context/{sanitizer_spec.rb → sanitize_spec.rb} +1 -1
- data/spec/lib/castle/core/get_connection_spec.rb +59 -0
- data/spec/lib/castle/{api/response_spec.rb → core/process_response_spec.rb} +56 -1
- data/spec/lib/castle/core/process_webhook_spec.rb +46 -0
- data/spec/lib/castle/core/send_request_spec.rb +102 -0
- data/spec/lib/castle/failover/strategy_spec.rb +12 -0
- data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +18 -18
- data/spec/lib/castle/{headers_filter_spec.rb → headers/filter_spec.rb} +6 -5
- data/spec/lib/castle/headers/format_spec.rb +25 -0
- data/spec/lib/castle/{extractors/ip_spec.rb → ip/extract_spec.rb} +35 -7
- data/spec/lib/castle/logger_spec.rb +42 -0
- data/spec/lib/castle/payload/prepare_spec.rb +54 -0
- data/spec/lib/castle/session_spec.rb +88 -0
- data/spec/lib/castle/singleton_configuration_spec.rb +18 -0
- data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +69 -0
- data/spec/lib/castle/utils/{cloner_spec.rb → clone_spec.rb} +3 -3
- data/spec/lib/castle/utils/deep_symbolize_keys_spec.rb +50 -0
- data/spec/lib/castle/utils/{timestamp_spec.rb → get_timestamp_spec.rb} +1 -1
- data/spec/lib/castle/utils/{merger_spec.rb → merge_spec.rb} +3 -3
- data/spec/lib/castle/verdict_spec.rb +9 -0
- data/spec/lib/castle/webhooks/verify_spec.rb +69 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/shared_examples/configuration.rb +129 -0
- metadata +133 -56
- data/lib/castle/api/request.rb +0 -42
- data/lib/castle/api/session.rb +0 -39
- data/lib/castle/commands/impersonate.rb +0 -26
- data/lib/castle/extractors/headers.rb +0 -45
- data/lib/castle/extractors/ip.rb +0 -68
- data/lib/castle/failover_auth_response.rb +0 -21
- data/lib/castle/headers_filter.rb +0 -35
- data/lib/castle/headers_formatter.rb +0 -22
- data/lib/castle/review.rb +0 -11
- data/lib/castle/utils.rb +0 -55
- data/lib/castle/utils/cloner.rb +0 -11
- data/lib/castle/utils/timestamp.rb +0 -12
- data/spec/lib/castle/api/request_spec.rb +0 -72
- data/spec/lib/castle/headers_formatter_spec.rb +0 -25
- data/spec/lib/castle/utils_spec.rb +0 -156
data/lib/castle/api/request.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
module API
|
5
|
-
# this class is responsible for making requests to api
|
6
|
-
module Request
|
7
|
-
# Default headers that we add to passed ones
|
8
|
-
DEFAULT_HEADERS = {
|
9
|
-
'Content-Type' => 'application/json'
|
10
|
-
}.freeze
|
11
|
-
|
12
|
-
private_constant :DEFAULT_HEADERS
|
13
|
-
|
14
|
-
class << self
|
15
|
-
def call(command, api_secret, headers)
|
16
|
-
Castle::API::Session.get.request(
|
17
|
-
build(
|
18
|
-
command,
|
19
|
-
headers.merge(DEFAULT_HEADERS),
|
20
|
-
api_secret
|
21
|
-
)
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
|
-
def build(command, headers, api_secret)
|
26
|
-
request_obj = Net::HTTP.const_get(
|
27
|
-
command.method.to_s.capitalize
|
28
|
-
).new("#{Castle.config.url_prefix}/#{command.path}", headers)
|
29
|
-
|
30
|
-
unless command.method == :get
|
31
|
-
request_obj.body = ::Castle::Utils.replace_invalid_characters(
|
32
|
-
command.data
|
33
|
-
).to_json
|
34
|
-
end
|
35
|
-
|
36
|
-
request_obj.basic_auth('', api_secret)
|
37
|
-
request_obj
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
data/lib/castle/api/session.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'singleton'
|
4
|
-
|
5
|
-
module Castle
|
6
|
-
module API
|
7
|
-
# this class keeps http config object
|
8
|
-
# and provides start/finish methods for persistent connection usage
|
9
|
-
# when there is a need of sending multiple requests at once
|
10
|
-
class Session
|
11
|
-
include Singleton
|
12
|
-
|
13
|
-
attr_accessor :http
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
reset
|
17
|
-
end
|
18
|
-
|
19
|
-
def reset
|
20
|
-
@http = Net::HTTP.new(Castle.config.host, Castle.config.port)
|
21
|
-
@http.read_timeout = Castle.config.request_timeout / 1000.0
|
22
|
-
|
23
|
-
if Castle.config.port == 443
|
24
|
-
@http.use_ssl = true
|
25
|
-
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
26
|
-
end
|
27
|
-
|
28
|
-
@http
|
29
|
-
end
|
30
|
-
|
31
|
-
class << self
|
32
|
-
# @return [Net::HTTP]
|
33
|
-
def get
|
34
|
-
instance.http
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
module Commands
|
5
|
-
# builder for impersonate command
|
6
|
-
class Impersonate
|
7
|
-
def initialize(context)
|
8
|
-
@context = context
|
9
|
-
end
|
10
|
-
|
11
|
-
def build(options = {})
|
12
|
-
Castle::Validators::Present.call(options, %i[user_id])
|
13
|
-
context = Castle::Context::Merger.call(@context, options[:context])
|
14
|
-
context = Castle::Context::Sanitizer.call(context)
|
15
|
-
|
16
|
-
Castle::Validators::Present.call(context, %i[user_agent ip])
|
17
|
-
|
18
|
-
Castle::Command.new(
|
19
|
-
'impersonate',
|
20
|
-
options.merge(context: context, sent_at: Castle::Utils::Timestamp.call),
|
21
|
-
options[:reset] ? :delete : :post
|
22
|
-
)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
module Extractors
|
5
|
-
# used for extraction of cookies and headers from the request
|
6
|
-
class Headers
|
7
|
-
# Headers that we will never scrub, even if they land on the configuration blacklist.
|
8
|
-
ALWAYS_WHITELISTED = %w[User-Agent].freeze
|
9
|
-
|
10
|
-
# Headers that will always be scrubbed, even if whitelisted.
|
11
|
-
ALWAYS_BLACKLISTED = %w[Cookie Authorization].freeze
|
12
|
-
|
13
|
-
private_constant :ALWAYS_WHITELISTED, :ALWAYS_BLACKLISTED
|
14
|
-
|
15
|
-
# @param headers [Hash]
|
16
|
-
def initialize(headers)
|
17
|
-
@headers = headers
|
18
|
-
@no_whitelist = Castle.config.whitelisted.empty?
|
19
|
-
end
|
20
|
-
|
21
|
-
# Serialize HTTP headers
|
22
|
-
# @return [Hash]
|
23
|
-
def call
|
24
|
-
@headers.each_with_object({}) do |(name, value), acc|
|
25
|
-
acc[name] = header_value(name, value)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
# scrub header value
|
32
|
-
# @param name [String]
|
33
|
-
# @param value [String]
|
34
|
-
# @return [TrueClass | FalseClass | String]
|
35
|
-
def header_value(name, value)
|
36
|
-
return true if ALWAYS_BLACKLISTED.include?(name)
|
37
|
-
return value if ALWAYS_WHITELISTED.include?(name)
|
38
|
-
return true if Castle.config.blacklisted.include?(name)
|
39
|
-
return value if @no_whitelist || Castle.config.whitelisted.include?(name)
|
40
|
-
|
41
|
-
true
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
data/lib/castle/extractors/ip.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
module Extractors
|
5
|
-
# used for extraction of ip from the request
|
6
|
-
class IP
|
7
|
-
# ordered list of ip headers for ip extraction
|
8
|
-
DEFAULT = %w[X-Forwarded-For Client-Ip Remote-Addr].freeze
|
9
|
-
# default header fallback when ip is not found
|
10
|
-
FALLBACK = 'Remote-Addr'
|
11
|
-
|
12
|
-
private_constant :FALLBACK, :DEFAULT
|
13
|
-
|
14
|
-
# @param headers [Hash]
|
15
|
-
def initialize(headers)
|
16
|
-
@headers = headers
|
17
|
-
@ip_headers = Castle.config.ip_headers + DEFAULT
|
18
|
-
@proxies = Castle.config.trusted_proxies + Castle::Configuration::TRUSTED_PROXIES
|
19
|
-
end
|
20
|
-
|
21
|
-
# Order of headers:
|
22
|
-
# .... list of headers defined by ip_headers
|
23
|
-
# X-Forwarded-For
|
24
|
-
# Client-Ip is
|
25
|
-
# Remote-Addr
|
26
|
-
# @return [String]
|
27
|
-
def call
|
28
|
-
@ip_headers.each do |ip_header|
|
29
|
-
ip_value = calculate_ip(ip_header)
|
30
|
-
return ip_value if ip_value
|
31
|
-
end
|
32
|
-
|
33
|
-
@headers[FALLBACK]
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
# @param header [String]
|
39
|
-
# @return [String]
|
40
|
-
def calculate_ip(header)
|
41
|
-
ips = ips_from(header)
|
42
|
-
remove_proxies(ips).first
|
43
|
-
end
|
44
|
-
|
45
|
-
# @param ips [Array<String>]
|
46
|
-
# @return [Array<String>]
|
47
|
-
def remove_proxies(ips)
|
48
|
-
ips.reject { |ip| proxy?(ip) }
|
49
|
-
end
|
50
|
-
|
51
|
-
# @param ip [String]
|
52
|
-
# @return [Boolean]
|
53
|
-
def proxy?(ip)
|
54
|
-
@proxies.any? { |proxy| proxy.match(ip) }
|
55
|
-
end
|
56
|
-
|
57
|
-
# @param header [String]
|
58
|
-
# @return [Array<String>]
|
59
|
-
def ips_from(header)
|
60
|
-
value = @headers[header]
|
61
|
-
|
62
|
-
return [] unless value
|
63
|
-
|
64
|
-
value.strip.split(/[,\s]+/).reverse
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
# generate failover authentication response
|
5
|
-
class FailoverAuthResponse
|
6
|
-
def initialize(user_id, strategy: Castle.config.failover_strategy, reason:)
|
7
|
-
@strategy = strategy
|
8
|
-
@reason = reason
|
9
|
-
@user_id = user_id
|
10
|
-
end
|
11
|
-
|
12
|
-
def generate
|
13
|
-
{
|
14
|
-
action: @strategy.to_s,
|
15
|
-
user_id: @user_id,
|
16
|
-
failover: true,
|
17
|
-
failover_reason: @reason
|
18
|
-
}
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
# used for preparing valuable headers list
|
5
|
-
class HeadersFilter
|
6
|
-
# headers filter
|
7
|
-
# HTTP_ - this is how Rack prefixes incoming HTTP headers
|
8
|
-
# CONTENT_LENGTH - for responses without Content-Length or Transfer-Encoding header
|
9
|
-
# REMOTE_ADDR - ip address header returned by web server
|
10
|
-
VALUABLE_HEADERS = /^
|
11
|
-
HTTP(?:_|-).*|
|
12
|
-
CONTENT(?:_|-)LENGTH|
|
13
|
-
REMOTE(?:_|-)ADDR
|
14
|
-
$/xi.freeze
|
15
|
-
|
16
|
-
private_constant :VALUABLE_HEADERS
|
17
|
-
|
18
|
-
# @param request [Rack::Request]
|
19
|
-
def initialize(request)
|
20
|
-
@request_env = request.env
|
21
|
-
@formatter = HeadersFormatter
|
22
|
-
end
|
23
|
-
|
24
|
-
# Serialize HTTP headers
|
25
|
-
# @return [Hash]
|
26
|
-
def call
|
27
|
-
@request_env.keys.each_with_object({}) do |header_name, acc|
|
28
|
-
next unless header_name.match(VALUABLE_HEADERS)
|
29
|
-
|
30
|
-
formatted_name = @formatter.call(header_name)
|
31
|
-
acc[formatted_name] = @request_env[header_name]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
# formats header name
|
5
|
-
class HeadersFormatter
|
6
|
-
class << self
|
7
|
-
# @param header [String]
|
8
|
-
# @return [String]
|
9
|
-
def call(header)
|
10
|
-
format(header.to_s.gsub(/^HTTP(?:_|-)/i, ''))
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
# @param header [String]
|
16
|
-
# @return [String]
|
17
|
-
def format(header)
|
18
|
-
header.split(/_|-/).map(&:capitalize).join('-')
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/castle/review.rb
DELETED
data/lib/castle/utils.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
module Utils
|
5
|
-
class << self
|
6
|
-
# Returns a new hash with all keys converted to symbols, as long as
|
7
|
-
# they respond to +to_sym+. This includes the keys from the root hash
|
8
|
-
# and from all nested hashes and arrays.
|
9
|
-
#
|
10
|
-
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
11
|
-
#
|
12
|
-
# Castle::Hash.deep_symbolize_keys(hash)
|
13
|
-
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
14
|
-
def deep_symbolize_keys(object, &block)
|
15
|
-
case object
|
16
|
-
when Hash
|
17
|
-
object.each_with_object({}) do |(key, value), result|
|
18
|
-
result[key.to_sym] = deep_symbolize_keys(value, &block)
|
19
|
-
end
|
20
|
-
when Array
|
21
|
-
object.map { |e| deep_symbolize_keys(e, &block) }
|
22
|
-
else
|
23
|
-
object
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def deep_symbolize_keys!(object, &block)
|
28
|
-
case object
|
29
|
-
when Hash
|
30
|
-
object.keys.each do |key|
|
31
|
-
value = object.delete(key)
|
32
|
-
object[key.to_sym] = deep_symbolize_keys!(value, &block)
|
33
|
-
end
|
34
|
-
object
|
35
|
-
when Array
|
36
|
-
object.map! { |e| deep_symbolize_keys!(e, &block) }
|
37
|
-
else
|
38
|
-
object
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def replace_invalid_characters(arg)
|
43
|
-
if arg.is_a?(::String)
|
44
|
-
arg.encode('UTF-8', invalid: :replace, undef: :replace)
|
45
|
-
elsif arg.is_a?(::Hash)
|
46
|
-
arg.each_with_object({}) { |(k, v), h| h[k] = replace_invalid_characters(v) }
|
47
|
-
elsif arg.is_a?(::Array)
|
48
|
-
arg.map(&method(:replace_invalid_characters))
|
49
|
-
else
|
50
|
-
arg
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/lib/castle/utils/cloner.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
describe Castle::API::Request do
|
4
|
-
describe '#call' do
|
5
|
-
subject(:call) { described_class.call(command, api_secret, headers) }
|
6
|
-
|
7
|
-
let(:session) { instance_double('Castle::API::Session') }
|
8
|
-
let(:http) { instance_double('Net::HTTP') }
|
9
|
-
let(:command) { Castle::Commands::Track.new({}).build(event: '$login.succeeded') }
|
10
|
-
let(:headers) { {} }
|
11
|
-
let(:api_secret) { 'secret' }
|
12
|
-
let(:request_build) { {} }
|
13
|
-
let(:expected_headers) { { 'Content-Type' => 'application/json' } }
|
14
|
-
|
15
|
-
before do
|
16
|
-
allow(Castle::API::Session).to receive(:instance).and_return(session)
|
17
|
-
allow(session).to receive(:http).and_return(http)
|
18
|
-
allow(http).to receive(:request)
|
19
|
-
allow(described_class).to receive(:build).and_return(request_build)
|
20
|
-
call
|
21
|
-
end
|
22
|
-
|
23
|
-
it do
|
24
|
-
expect(described_class).to have_received(:build).with(command, expected_headers, api_secret)
|
25
|
-
end
|
26
|
-
|
27
|
-
it { expect(http).to have_received(:request).with(request_build) }
|
28
|
-
end
|
29
|
-
|
30
|
-
describe '#build' do
|
31
|
-
subject(:build) { described_class.build(command, headers, api_secret) }
|
32
|
-
|
33
|
-
let(:headers) { { 'SAMPLE-HEADER' => '1' } }
|
34
|
-
let(:api_secret) { 'secret' }
|
35
|
-
|
36
|
-
context 'when get' do
|
37
|
-
let(:command) { Castle::Commands::Review.build(review_id) }
|
38
|
-
let(:review_id) { SecureRandom.uuid }
|
39
|
-
|
40
|
-
it { expect(build.body).to be_nil }
|
41
|
-
it { expect(build.method).to eql('GET') }
|
42
|
-
it { expect(build.path).to eql("/v1/#{command.path}") }
|
43
|
-
it { expect(build.to_hash).to have_key('authorization') }
|
44
|
-
it { expect(build.to_hash).to have_key('sample-header') }
|
45
|
-
it { expect(build.to_hash['sample-header']).to eql(['1']) }
|
46
|
-
end
|
47
|
-
|
48
|
-
context 'when post' do
|
49
|
-
let(:time) { Time.now.utc.iso8601(3) }
|
50
|
-
let(:command) do
|
51
|
-
Castle::Commands::Track.new({}).build(event: '$login.succeeded', name: "\xC4")
|
52
|
-
end
|
53
|
-
let(:expected_body) do
|
54
|
-
{
|
55
|
-
event: '$login.succeeded',
|
56
|
-
name: '�',
|
57
|
-
context: {},
|
58
|
-
sent_at: time
|
59
|
-
}
|
60
|
-
end
|
61
|
-
|
62
|
-
before { allow(Castle::Utils::Timestamp).to receive(:call).and_return(time) }
|
63
|
-
|
64
|
-
it { expect(build.body).to be_eql(expected_body.to_json) }
|
65
|
-
it { expect(build.method).to eql('POST') }
|
66
|
-
it { expect(build.path).to eql("/v1/#{command.path}") }
|
67
|
-
it { expect(build.to_hash).to have_key('authorization') }
|
68
|
-
it { expect(build.to_hash).to have_key('sample-header') }
|
69
|
-
it { expect(build.to_hash['sample-header']).to eql(['1']) }
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|