locked-rb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +127 -0
- data/lib/locked-rb.rb +3 -0
- data/lib/locked.rb +60 -0
- data/lib/locked/api.rb +40 -0
- data/lib/locked/api/request.rb +37 -0
- data/lib/locked/api/request/build.rb +29 -0
- data/lib/locked/api/response.rb +40 -0
- data/lib/locked/client.rb +66 -0
- data/lib/locked/command.rb +5 -0
- data/lib/locked/commands/authenticate.rb +23 -0
- data/lib/locked/commands/identify.rb +23 -0
- data/lib/locked/commands/review.rb +14 -0
- data/lib/locked/configuration.rb +75 -0
- data/lib/locked/context/default.rb +40 -0
- data/lib/locked/context/merger.rb +14 -0
- data/lib/locked/context/sanitizer.rb +23 -0
- data/lib/locked/errors.rb +41 -0
- data/lib/locked/extractors/client_id.rb +17 -0
- data/lib/locked/extractors/headers.rb +24 -0
- data/lib/locked/extractors/ip.rb +18 -0
- data/lib/locked/failover_auth_response.rb +23 -0
- data/lib/locked/header_formatter.rb +9 -0
- data/lib/locked/review.rb +11 -0
- data/lib/locked/secure_mode.rb +11 -0
- data/lib/locked/support/hanami.rb +19 -0
- data/lib/locked/support/padrino.rb +19 -0
- data/lib/locked/support/rails.rb +13 -0
- data/lib/locked/support/sinatra.rb +19 -0
- data/lib/locked/utils.rb +55 -0
- data/lib/locked/utils/cloner.rb +11 -0
- data/lib/locked/utils/merger.rb +23 -0
- data/lib/locked/utils/timestamp.rb +12 -0
- data/lib/locked/validators/not_supported.rb +16 -0
- data/lib/locked/validators/present.rb +16 -0
- data/lib/locked/version.rb +5 -0
- data/spec/lib/Locked/api/request/build_spec.rb +42 -0
- data/spec/lib/Locked/api/request_spec.rb +59 -0
- data/spec/lib/Locked/api/response_spec.rb +58 -0
- data/spec/lib/Locked/api_spec.rb +37 -0
- data/spec/lib/Locked/client_spec.rb +226 -0
- data/spec/lib/Locked/command_spec.rb +9 -0
- data/spec/lib/Locked/commands/authenticate_spec.rb +95 -0
- data/spec/lib/Locked/commands/identify_spec.rb +87 -0
- data/spec/lib/Locked/commands/review_spec.rb +24 -0
- data/spec/lib/Locked/configuration_spec.rb +146 -0
- data/spec/lib/Locked/context/default_spec.rb +35 -0
- data/spec/lib/Locked/context/merger_spec.rb +23 -0
- data/spec/lib/Locked/context/sanitizer_spec.rb +27 -0
- data/spec/lib/Locked/extractors/client_id_spec.rb +62 -0
- data/spec/lib/Locked/extractors/headers_spec.rb +26 -0
- data/spec/lib/Locked/extractors/ip_spec.rb +27 -0
- data/spec/lib/Locked/header_formatter_spec.rb +25 -0
- data/spec/lib/Locked/review_spec.rb +19 -0
- data/spec/lib/Locked/secure_mode_spec.rb +9 -0
- data/spec/lib/Locked/utils/cloner_spec.rb +18 -0
- data/spec/lib/Locked/utils/merger_spec.rb +13 -0
- data/spec/lib/Locked/utils/timestamp_spec.rb +17 -0
- data/spec/lib/Locked/utils_spec.rb +156 -0
- data/spec/lib/Locked/validators/not_supported_spec.rb +26 -0
- data/spec/lib/Locked/validators/present_spec.rb +33 -0
- data/spec/lib/Locked/version_spec.rb +5 -0
- data/spec/lib/locked_spec.rb +66 -0
- data/spec/spec_helper.rb +22 -0
- metadata +133 -0
data/lib/locked/utils.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
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
|
+
# Locked::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
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Utils
|
5
|
+
class Merger
|
6
|
+
def self.call(base, extra)
|
7
|
+
base_s = Locked::Utils.deep_symbolize_keys(base)
|
8
|
+
extra_s = Locked::Utils.deep_symbolize_keys(extra)
|
9
|
+
|
10
|
+
extra_s.each do |name, value|
|
11
|
+
if value.nil?
|
12
|
+
base_s.delete(name)
|
13
|
+
elsif value.is_a?(Hash) && base_s[name].is_a?(Hash)
|
14
|
+
base_s[name] = call(base_s[name], value)
|
15
|
+
else
|
16
|
+
base_s[name] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
base_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Validators
|
5
|
+
class NotSupported
|
6
|
+
class << self
|
7
|
+
def call(options, keys)
|
8
|
+
keys.each do |key|
|
9
|
+
next unless options.key?(key)
|
10
|
+
raise Locked::InvalidParametersError, "#{key} is/are not supported"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Locked
|
4
|
+
module Validators
|
5
|
+
class Present
|
6
|
+
class << self
|
7
|
+
def call(options, keys)
|
8
|
+
keys.each do |key|
|
9
|
+
next unless options[key].to_s.empty?
|
10
|
+
raise Locked::InvalidParametersError, "#{key} is missing or empty"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::API::Request::Build do
|
4
|
+
subject(:call) { described_class.call(command, headers, api_key) }
|
5
|
+
|
6
|
+
let(:headers) { { 'SAMPLE-HEADER' => '1' } }
|
7
|
+
let(:api_key) { 'key' }
|
8
|
+
|
9
|
+
describe 'call' do
|
10
|
+
# context 'when get' do
|
11
|
+
# let(:command) { Locked::Commands::Review.build(review_id) }
|
12
|
+
# let(:review_id) { SecureRandom.uuid }
|
13
|
+
#
|
14
|
+
# it { expect(call.body).to be_nil }
|
15
|
+
# it { expect(call.method).to eql('GET') }
|
16
|
+
# it { expect(call.path).to eql("/api/v1/client#{command.path}") }
|
17
|
+
# it { expect(call.to_hash).to have_key('authorization') }
|
18
|
+
# it { expect(call.to_hash).to have_key('sample-header') }
|
19
|
+
# it { expect(call.to_hash['sample-header']).to eql(['1']) }
|
20
|
+
# end
|
21
|
+
|
22
|
+
context 'when post' do
|
23
|
+
let(:time) { Time.now.utc.iso8601(3) }
|
24
|
+
let(:command) { Locked::Commands::Authenticate.new({}).build(event: '$login.success', name: "test") }
|
25
|
+
let(:expected_body) do
|
26
|
+
{
|
27
|
+
event: '$login.success',
|
28
|
+
name: "test",
|
29
|
+
# context: {},
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
before { allow(Locked::Utils::Timestamp).to receive(:call).and_return(time) }
|
34
|
+
|
35
|
+
it { expect(call.body).to be_eql(expected_body.to_json) }
|
36
|
+
it { expect(call.method).to eql('POST') }
|
37
|
+
it { expect(call.path).to eql("/api/v1/client/#{command.path}") }
|
38
|
+
it { expect(call.to_hash).to have_key('sample-header') }
|
39
|
+
it { expect(call.to_hash['sample-header']).to eql(['1']) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::API::Request do
|
4
|
+
describe '#call' do
|
5
|
+
subject(:call) { described_class.call(command, api_key, headers) }
|
6
|
+
|
7
|
+
let(:http) { instance_double('Net::HTTP') }
|
8
|
+
let(:request_build) { instance_double('Locked::API::Request::Build') }
|
9
|
+
let(:command) { Locked::Commands::Authenticate.new({}).build(event: '$login.success') }
|
10
|
+
let(:headers) { {} }
|
11
|
+
let(:api_key) { 'key' }
|
12
|
+
let(:expected_headers) { { 'Content-Type' => 'application/json' } }
|
13
|
+
|
14
|
+
before do
|
15
|
+
allow(described_class).to receive(:http).and_return(http)
|
16
|
+
allow(http).to receive(:request).with(request_build)
|
17
|
+
allow(Locked::API::Request::Build).to receive(:call)
|
18
|
+
.with(command, expected_headers, api_key)
|
19
|
+
.and_return(request_build)
|
20
|
+
call
|
21
|
+
end
|
22
|
+
|
23
|
+
it do
|
24
|
+
expect(Locked::API::Request::Build).to have_received(:call)
|
25
|
+
.with(command, expected_headers, api_key)
|
26
|
+
end
|
27
|
+
it { expect(http).to have_received(:request).with(request_build) }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#http' do
|
31
|
+
subject(:http) { described_class.http }
|
32
|
+
|
33
|
+
context 'when ssl false' do
|
34
|
+
before do
|
35
|
+
Locked.config.host = 'localhost'
|
36
|
+
Locked.config.port = 3002
|
37
|
+
end
|
38
|
+
|
39
|
+
after do
|
40
|
+
Locked.config.host = Locked::Configuration::HOST
|
41
|
+
Locked.config.port = Locked::Configuration::PORT
|
42
|
+
end
|
43
|
+
|
44
|
+
it { expect(http).to be_instance_of(Net::HTTP) }
|
45
|
+
it { expect(http.address).to eq(Locked.config.host) }
|
46
|
+
it { expect(http.port).to eq(Locked.config.port) }
|
47
|
+
it { expect(http.use_ssl?).to be false }
|
48
|
+
it { expect(http.verify_mode).to be_nil }
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when ssl true' do
|
52
|
+
it { expect(http).to be_instance_of(Net::HTTP) }
|
53
|
+
it { expect(http.address).to eq(Locked.config.host) }
|
54
|
+
it { expect(http.port).to eq(Locked.config.port) }
|
55
|
+
it { expect(http.use_ssl?).to be true }
|
56
|
+
it { expect(http.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::API::Response do
|
4
|
+
describe '#call' do
|
5
|
+
subject(:call) { described_class.call(response) }
|
6
|
+
|
7
|
+
context 'when success' do
|
8
|
+
let(:response) { OpenStruct.new(body: '{"user":1}', code: 200) }
|
9
|
+
|
10
|
+
it { expect(call).to eql(user: 1) }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when response empty' do
|
14
|
+
let(:response) { OpenStruct.new(body: '', code: 200) }
|
15
|
+
|
16
|
+
it { expect(call).to eql({}) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when response nil' do
|
20
|
+
let(:response) { OpenStruct.new(code: 200) }
|
21
|
+
|
22
|
+
it { expect(call).to eql({}) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when json is malformed' do
|
26
|
+
let(:response) { OpenStruct.new(body: '{a', code: 200) }
|
27
|
+
|
28
|
+
it { expect { call }.to raise_error(Locked::ApiError) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#verify!' do
|
33
|
+
subject(:verify!) { described_class.verify!(response) }
|
34
|
+
|
35
|
+
context 'without error when response is 2xx' do
|
36
|
+
let(:response) { OpenStruct.new(code: 200) }
|
37
|
+
|
38
|
+
it { expect { verify! }.not_to raise_error }
|
39
|
+
end
|
40
|
+
|
41
|
+
shared_examples 'response_failed' do |code, error|
|
42
|
+
let(:response) { OpenStruct.new(code: code) }
|
43
|
+
|
44
|
+
it "fail when response is #{code}" do
|
45
|
+
expect { verify! }.to raise_error(error)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it_behaves_like 'response_failed', '400', Locked::BadRequestError
|
50
|
+
it_behaves_like 'response_failed', '401', Locked::UnauthorizedError
|
51
|
+
it_behaves_like 'response_failed', '403', Locked::ForbiddenError
|
52
|
+
it_behaves_like 'response_failed', '404', Locked::NotFoundError
|
53
|
+
it_behaves_like 'response_failed', '419', Locked::UserUnauthorizedError
|
54
|
+
it_behaves_like 'response_failed', '422', Locked::InvalidParametersError
|
55
|
+
it_behaves_like 'response_failed', '499', Locked::ApiError
|
56
|
+
it_behaves_like 'response_failed', '500', Locked::InternalServerError
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::API do
|
4
|
+
subject(:request) { described_class.request(command) }
|
5
|
+
|
6
|
+
let(:command) { Locked::Commands::Authenticate.new({}).build(event: '$login.success') }
|
7
|
+
|
8
|
+
context 'when request timeouts' do
|
9
|
+
before { stub_request(:any, /locked.jp/).to_timeout }
|
10
|
+
|
11
|
+
it do
|
12
|
+
expect do
|
13
|
+
request
|
14
|
+
end.to raise_error(Locked::RequestError)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when non-OK response code' do
|
19
|
+
before { stub_request(:any, /locked.jp/).to_return(status: 400) }
|
20
|
+
|
21
|
+
it do
|
22
|
+
expect do
|
23
|
+
request
|
24
|
+
end.to raise_error(Locked::BadRequestError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when no api_key' do
|
29
|
+
before { allow(Locked.config).to receive(:api_key).and_return('') }
|
30
|
+
|
31
|
+
it do
|
32
|
+
expect do
|
33
|
+
request
|
34
|
+
end.to raise_error(Locked::ConfigurationError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Client 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
|
+
'X-LOCKED-API-KEY' => 'key'
|
14
|
+
)
|
15
|
+
end
|
16
|
+
let(:request) { Rack::Request.new(env) }
|
17
|
+
let(:client) { described_class.from_request(request) }
|
18
|
+
let(:request_to_context) { described_class.to_context(request) }
|
19
|
+
let(:client_with_user_timestamp) do
|
20
|
+
described_class.new(request_to_context, timestamp: time_user)
|
21
|
+
end
|
22
|
+
let(:client_with_no_timestamp) { described_class.new(request_to_context) }
|
23
|
+
|
24
|
+
let(:headers) { { 'X-Forwarded-For' => ip.to_s, 'User-Agent' => ua } }
|
25
|
+
let(:context) do
|
26
|
+
{
|
27
|
+
client_id: 'abcd',
|
28
|
+
active: true,
|
29
|
+
origin: 'web',
|
30
|
+
user_agent: ua,
|
31
|
+
headers: { 'X-Forwarded-For': ip.to_s, 'User-Agent': ua },
|
32
|
+
ip: ip,
|
33
|
+
library: { name: 'locked-rb', version: '0.0.1' }
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:time_now) { Time.now }
|
38
|
+
let(:time_auto) { time_now.utc.iso8601(3) }
|
39
|
+
let(:time_user) { (Time.now - 10_000).utc.iso8601(3) }
|
40
|
+
let(:response_body) { {}.to_json }
|
41
|
+
|
42
|
+
before do
|
43
|
+
Timecop.freeze(time_now)
|
44
|
+
stub_const('Locked::VERSION', '0.0.1')
|
45
|
+
stub_request(:any, /locked.jp/).to_return(status: 200, body: response_body, headers: {})
|
46
|
+
end
|
47
|
+
|
48
|
+
after { Timecop.return }
|
49
|
+
|
50
|
+
describe 'parses the request' do
|
51
|
+
before do
|
52
|
+
allow(Locked::API).to receive(:request).and_call_original
|
53
|
+
end
|
54
|
+
|
55
|
+
it do
|
56
|
+
client.authenticate(event: '$login.success', user_id: '1234')
|
57
|
+
expect(Locked::API).to have_received(:request)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'to_context' do
|
62
|
+
it do
|
63
|
+
expect(described_class.to_context(request)).to eql(context)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'to_options' do
|
68
|
+
let(:options) { { user_id: '1234', user_traits: { name: 'Jo' } } }
|
69
|
+
let(:result) { { user_id: '1234', user_traits: { name: 'Jo' }, timestamp: time_auto } }
|
70
|
+
|
71
|
+
it do
|
72
|
+
expect(described_class.to_options(options)).to eql(result)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# describe 'identify' do
|
77
|
+
# let(:request_body) do
|
78
|
+
# { user_id: '1234', timestamp: time_auto,
|
79
|
+
# sent_at: time_auto, context: context, user_traits: { name: 'Jo' } }
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# before { client.identify(options) }
|
83
|
+
#
|
84
|
+
# context 'when used with symbol keys' do
|
85
|
+
# let(:options) { { user_id: '1234', user_traits: { name: 'Jo' } } }
|
86
|
+
#
|
87
|
+
# it do
|
88
|
+
# assert_requested :post, 'https://locked.jp/api/v1/client/identify', times: 1 do |req|
|
89
|
+
# JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# context 'when passed timestamp in options and no defined timestamp' do
|
94
|
+
# let(:client) { client_with_no_timestamp }
|
95
|
+
# let(:options) { { user_id: '1234', user_traits: { name: 'Jo' }, timestamp: time_user } }
|
96
|
+
# let(:request_body) do
|
97
|
+
# { user_id: '1234', user_traits: { name: 'Jo' }, context: context,
|
98
|
+
# timestamp: time_user, sent_at: time_auto }
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# it do
|
102
|
+
# assert_requested :post, 'https://locked.jp/api/v1/client/identify', times: 1 do |req|
|
103
|
+
# JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# context 'with client initialized with timestamp' do
|
109
|
+
# let(:client) { client_with_user_timestamp }
|
110
|
+
# let(:request_body) do
|
111
|
+
# { user_id: '1234', timestamp: time_user, sent_at: time_auto,
|
112
|
+
# context: context, user_traits: { name: 'Jo' } }
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# it do
|
116
|
+
# assert_requested :post, 'https://locked.jp/api/v1/client/identify', times: 1 do |req|
|
117
|
+
# JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# context 'when used with string keys' do
|
124
|
+
# let(:options) { { 'user_id' => '1234', 'user_traits' => { 'name' => 'Jo' } } }
|
125
|
+
#
|
126
|
+
# it do
|
127
|
+
# assert_requested :post, 'https://locked.jp/api/v1/client/identify', times: 1 do |req|
|
128
|
+
# JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
|
134
|
+
describe 'authenticate' do
|
135
|
+
let(:options) { { event: '$login.success', user_id: '1234' } }
|
136
|
+
let(:request_response) { client.authenticate(options) }
|
137
|
+
let(:request_body) do
|
138
|
+
{ event: '$login.success', user_id: '1234', timestamp: time_auto }
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'when used with symbol keys' do
|
142
|
+
before { request_response }
|
143
|
+
|
144
|
+
it do
|
145
|
+
assert_requested :post, 'https://locked.jp/api/v1/client/authenticate', times: 1 do |req|
|
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.success', user_id: '1234', timestamp: time_user } }
|
153
|
+
let(:request_body) do
|
154
|
+
{ event: '$login.success', user_id: '1234', timestamp: time_user }
|
155
|
+
end
|
156
|
+
|
157
|
+
it do
|
158
|
+
assert_requested :post, 'https://locked.jp/api/v1/client/authenticate', times: 1 do |req|
|
159
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'with client initialized with timestamp' do
|
165
|
+
let(:client) { client_with_user_timestamp }
|
166
|
+
let(:request_body) do
|
167
|
+
{ event: '$login.success', user_id: '1234', timestamp: time_user }
|
168
|
+
end
|
169
|
+
|
170
|
+
it do
|
171
|
+
assert_requested :post, 'https://locked.jp/api/v1/client/authenticate', times: 1 do |req|
|
172
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'when used with string keys' do
|
179
|
+
let(:options) { { 'event' => '$login.success', 'user_id' => '1234' } }
|
180
|
+
|
181
|
+
before { request_response }
|
182
|
+
|
183
|
+
it do
|
184
|
+
assert_requested :post, 'https://locked.jp/api/v1/client/authenticate', times: 1 do |req|
|
185
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'when request with fail' do
|
191
|
+
before { allow(Locked::API).to receive(:request).and_raise(Locked::RequestError.new(Timeout::Error)) }
|
192
|
+
|
193
|
+
context 'with request error and throw strategy' do
|
194
|
+
before { allow(Locked.config).to receive(:failover_strategy).and_return(:throw) }
|
195
|
+
|
196
|
+
it { expect { request_response }.to raise_error(Locked::RequestError) }
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'with request error and not throw on eg deny strategy' do
|
200
|
+
it { assert_not_requested :post, 'https://locked.jp/api/v1/client/authenticate' }
|
201
|
+
it { expect(request_response[:data][:action]).to be_eql('deny') }
|
202
|
+
it { expect(request_response[:data][:user_id]).to be_eql('1234') }
|
203
|
+
it { expect(request_response[:failover]).to be true }
|
204
|
+
it { expect(request_response[:failover_reason]).to be_eql('Locked::RequestError') }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'when request is internal server error' do
|
209
|
+
before { allow(Locked::API).to receive(:request).and_raise(Locked::InternalServerError) }
|
210
|
+
|
211
|
+
describe 'throw strategy' do
|
212
|
+
before { allow(Locked.config).to receive(:failover_strategy).and_return(:throw) }
|
213
|
+
|
214
|
+
it { expect { request_response }.to raise_error(Locked::InternalServerError) }
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'not throw on eg deny strategy' do
|
218
|
+
it { assert_not_requested :post, 'https://locked.jp/api/v1/client/authenticate' }
|
219
|
+
it { expect(request_response[:data][:action]).to be_eql('deny') }
|
220
|
+
it { expect(request_response[:data][:user_id]).to be_eql('1234') }
|
221
|
+
it { expect(request_response[:failover]).to be true }
|
222
|
+
it { expect(request_response[:failover_reason]).to be_eql('Locked::InternalServerError') }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|