castle-rb 6.0.1 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -8
  3. data/lib/castle/api/approve_device.rb +1 -6
  4. data/lib/castle/api/authenticate.rb +10 -7
  5. data/lib/castle/api/end_impersonation.rb +3 -8
  6. data/lib/castle/api/filter.rb +37 -0
  7. data/lib/castle/api/get_device.rb +1 -6
  8. data/lib/castle/api/get_devices_for_user.rb +1 -6
  9. data/lib/castle/api/log.rb +37 -0
  10. data/lib/castle/api/report_device.rb +1 -6
  11. data/lib/castle/api/risk.rb +37 -0
  12. data/lib/castle/api/start_impersonation.rb +3 -8
  13. data/lib/castle/api/track.rb +1 -6
  14. data/lib/castle/api.rb +7 -12
  15. data/lib/castle/client.rb +36 -16
  16. data/lib/castle/commands/approve_device.rb +1 -5
  17. data/lib/castle/commands/end_impersonation.rb +1 -1
  18. data/lib/castle/commands/{identify.rb → filter.rb} +3 -3
  19. data/lib/castle/commands/get_device.rb +1 -5
  20. data/lib/castle/commands/get_devices_for_user.rb +1 -5
  21. data/lib/castle/commands/log.rb +22 -0
  22. data/lib/castle/commands/report_device.rb +1 -5
  23. data/lib/castle/commands/risk.rb +22 -0
  24. data/lib/castle/commands/start_impersonation.rb +1 -1
  25. data/lib/castle/configuration.rb +18 -8
  26. data/lib/castle/core/get_connection.rb +3 -1
  27. data/lib/castle/core/process_response.rb +5 -2
  28. data/lib/castle/core/process_webhook.rb +10 -5
  29. data/lib/castle/core/send_request.rb +8 -18
  30. data/lib/castle/errors.rb +37 -13
  31. data/lib/castle/failover/prepare_response.rb +6 -1
  32. data/lib/castle/failover/strategy.rb +3 -0
  33. data/lib/castle/headers/extract.rb +4 -4
  34. data/lib/castle/headers/filter.rb +9 -6
  35. data/lib/castle/ips/extract.rb +4 -2
  36. data/lib/castle/logger.rb +3 -3
  37. data/lib/castle/payload/prepare.rb +3 -4
  38. data/lib/castle/secure_mode.rb +3 -2
  39. data/lib/castle/support/hanami.rb +2 -6
  40. data/lib/castle/support/rails.rb +1 -3
  41. data/lib/castle/utils/clean_invalid_chars.rb +1 -3
  42. data/lib/castle/verdict.rb +2 -0
  43. data/lib/castle/version.rb +1 -1
  44. data/lib/castle/webhooks/verify.rb +9 -7
  45. data/lib/castle.rb +7 -11
  46. data/spec/integration/rails/rails_spec.rb +9 -7
  47. data/spec/integration/rails/support/home_controller.rb +26 -24
  48. data/spec/lib/castle/api/approve_device_spec.rb +3 -3
  49. data/spec/lib/castle/api/authenticate_spec.rb +20 -24
  50. data/spec/lib/castle/api/end_impersonation_spec.rb +11 -5
  51. data/spec/lib/castle/api/filter_spec.rb +5 -0
  52. data/spec/lib/castle/api/get_device_spec.rb +3 -3
  53. data/spec/lib/castle/api/get_devices_for_user_spec.rb +3 -3
  54. data/spec/lib/castle/api/log_spec.rb +5 -0
  55. data/spec/lib/castle/api/report_device_spec.rb +3 -3
  56. data/spec/lib/castle/api/risk_spec.rb +5 -0
  57. data/spec/lib/castle/api/start_impersonation_spec.rb +11 -5
  58. data/spec/lib/castle/api/track_spec.rb +11 -7
  59. data/spec/lib/castle/api_spec.rb +4 -20
  60. data/spec/lib/castle/client_id/extract_spec.rb +4 -13
  61. data/spec/lib/castle/client_spec.rb +84 -84
  62. data/spec/lib/castle/commands/authenticate_spec.rb +8 -15
  63. data/spec/lib/castle/commands/end_impersonation_spec.rb +6 -9
  64. data/spec/lib/castle/commands/{identify_spec.rb → filter_spec.rb} +27 -32
  65. data/spec/lib/castle/commands/log_spec.rb +73 -0
  66. data/spec/lib/castle/commands/risk_spec.rb +73 -0
  67. data/spec/lib/castle/commands/start_impersonation_spec.rb +6 -9
  68. data/spec/lib/castle/commands/track_spec.rb +9 -18
  69. data/spec/lib/castle/configuration_spec.rb +2 -6
  70. data/spec/lib/castle/context/get_default_spec.rb +8 -8
  71. data/spec/lib/castle/context/prepare_spec.rb +6 -7
  72. data/spec/lib/castle/core/get_connection_spec.rb +6 -22
  73. data/spec/lib/castle/core/process_response_spec.rb +1 -8
  74. data/spec/lib/castle/core/send_request_spec.rb +26 -27
  75. data/spec/lib/castle/headers/extract_spec.rb +1 -3
  76. data/spec/lib/castle/headers/filter_spec.rb +12 -11
  77. data/spec/lib/castle/ips/extract_spec.rb +4 -13
  78. data/spec/lib/castle/logger_spec.rb +2 -6
  79. data/spec/lib/castle/payload/prepare_spec.rb +5 -4
  80. data/spec/lib/castle/session_spec.rb +13 -36
  81. data/spec/lib/castle/singleton_configuration_spec.rb +2 -6
  82. data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +2 -2
  83. data/spec/lib/castle/utils/merge_spec.rb +3 -1
  84. data/spec/lib/castle/validators/present_spec.rb +5 -6
  85. data/spec/lib/castle/webhooks/verify_spec.rb +8 -24
  86. data/spec/lib/castle_spec.rb +4 -10
  87. data/spec/spec_helper.rb +1 -3
  88. data/spec/support/shared_examples/action_request.rb +155 -0
  89. data/spec/support/shared_examples/configuration.rb +14 -42
  90. metadata +23 -18
  91. data/lib/castle/api/identify.rb +0 -26
  92. data/lib/castle/api/review.rb +0 -24
  93. data/lib/castle/commands/review.rb +0 -17
  94. data/lib/castle/events.rb +0 -49
  95. data/spec/lib/castle/api/identify_spec.rb +0 -68
  96. data/spec/lib/castle/api/review_spec.rb +0 -19
  97. data/spec/lib/castle/commands/review_spec.rb +0 -24
  98. data/spec/lib/castle/events_spec.rb +0 -5
@@ -7,11 +7,13 @@ describe Castle::Context::GetDefault do
7
7
  let(:client_id) { 'abcd' }
8
8
 
9
9
  let(:env) do
10
- Rack::MockRequest.env_for('/',
11
- 'HTTP_X_FORWARDED_FOR' => ip,
12
- 'HTTP_ACCEPT_LANGUAGE' => 'en',
13
- 'HTTP_USER_AGENT' => 'test',
14
- 'HTTP_COOKIE' => "__cid=#{client_id};other=efgh")
10
+ Rack::MockRequest.env_for(
11
+ '/',
12
+ 'HTTP_X_FORWARDED_FOR' => ip,
13
+ 'HTTP_ACCEPT_LANGUAGE' => 'en',
14
+ 'HTTP_USER_AGENT' => 'test',
15
+ 'HTTP_COOKIE' => "__cid=#{client_id};other=efgh"
16
+ )
15
17
  end
16
18
  let(:request) { Rack::Request.new(env) }
17
19
  let(:default_context) { subject.call }
@@ -26,9 +28,7 @@ describe Castle::Context::GetDefault do
26
28
  }
27
29
  end
28
30
 
29
- before do
30
- stub_const('Castle::VERSION', version)
31
- end
31
+ before { stub_const('Castle::VERSION', version) }
32
32
 
33
33
  it { expect(default_context[:active]).to be_eql(true) }
34
34
  it { expect(default_context[:headers]).to be_eql(result_headers) }
@@ -20,19 +20,18 @@ describe Castle::Context::Prepare do
20
20
  user_agent: ua,
21
21
  headers: headers,
22
22
  ip: ip,
23
- library: { name: 'castle-rb', version: '6.0.0' }
23
+ library: {
24
+ name: 'castle-rb',
25
+ version: '6.0.0'
26
+ }
24
27
  }
25
28
  end
26
29
 
27
30
  let(:headers) do
28
- {
29
- 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true
30
- }
31
+ { 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true }
31
32
  end
32
33
 
33
- before do
34
- stub_const('Castle::VERSION', '6.0.0')
35
- end
34
+ before { stub_const('Castle::VERSION', '6.0.0') }
36
35
 
37
36
  describe '#call' do
38
37
  subject(:generated) { described_class.call(request) }
@@ -12,47 +12,31 @@ describe Castle::Core::GetConnection do
12
12
  before do
13
13
  Castle.config.base_url = 'http://localhost:3002'
14
14
 
15
- allow(Net::HTTP)
16
- .to receive(:new)
17
- .with(localhost, port)
18
- .and_call_original
15
+ allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original
19
16
  end
20
17
 
21
18
  it do
22
19
  class_call
23
20
 
24
- expect(Net::HTTP)
25
- .to have_received(:new)
26
- .with(localhost, port)
21
+ expect(Net::HTTP).to have_received(:new).with(localhost, port)
27
22
  end
28
23
 
29
- it do
30
- expect(class_call).to be_an_instance_of(Net::HTTP)
31
- end
24
+ it { expect(class_call).to be_an_instance_of(Net::HTTP) }
32
25
  end
33
26
 
34
27
  context 'when ssl true' do
35
28
  let(:localhost) { 'localhost' }
36
29
  let(:port) { 443 }
37
30
 
38
- before do
39
- Castle.config.base_url = 'https://localhost'
40
- end
31
+ before { Castle.config.base_url = 'https://localhost' }
41
32
 
42
33
  context 'with block' do
43
34
  let(:api_url) { '/test' }
44
35
  let(:request) { Net::HTTP::Get.new(api_url) }
45
36
 
46
- before do
47
- allow(Net::HTTP)
48
- .to receive(:new)
49
- .with(localhost, port)
50
- .and_call_original
51
- end
37
+ before { allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original }
52
38
 
53
- it do
54
- expect(class_call).to be_an_instance_of(Net::HTTP)
55
- end
39
+ it { expect(class_call).to be_an_instance_of(Net::HTTP) }
56
40
  end
57
41
  end
58
42
  end
@@ -38,14 +38,7 @@ describe Castle::Core::ProcessResponse do
38
38
  '{"action":"deny","user_id":"1","device_token":"abc",
39
39
  "risk_policy":{"id":"123","revision_id":"abc","name":"def","type":"bot"}}'
40
40
  end
41
- let(:response) do
42
- OpenStruct.new(
43
- {
44
- body: body,
45
- code: 200
46
- }
47
- )
48
- end
41
+ let(:response) { OpenStruct.new({ body: body, code: 200 }) }
49
42
 
50
43
  let(:result) do
51
44
  {
@@ -27,9 +27,7 @@ describe Castle::Core::SendRequest do
27
27
  end
28
28
 
29
29
  it do
30
- expect(described_class).to have_received(:build).with(
31
- command, expected_headers, config
32
- )
30
+ expect(described_class).to have_received(:build).with(command, expected_headers, config)
33
31
  end
34
32
 
35
33
  it { expect(http).to have_received(:request).with(request_build) }
@@ -48,9 +46,7 @@ describe Castle::Core::SendRequest do
48
46
  it { expect(Castle::Core::GetConnection).not_to have_received(:call) }
49
47
 
50
48
  it do
51
- expect(described_class).to have_received(:build).with(
52
- command, expected_headers, config
53
- )
49
+ expect(described_class).to have_received(:build).with(command, expected_headers, config)
54
50
  end
55
51
 
56
52
  it { expect(http).to have_received(:request).with(request_build) }
@@ -63,40 +59,43 @@ describe Castle::Core::SendRequest do
63
59
  let(:headers) { { 'SAMPLE-HEADER' => '1' } }
64
60
  let(:api_secret) { 'secret' }
65
61
 
66
- context 'when get' do
67
- let(:command) { Castle::Commands::Review.build({ review_id: review_id }) }
68
- let(:review_id) { SecureRandom.uuid }
62
+ context 'when post' do
63
+ let(:time) { Time.now.utc.iso8601(3) }
64
+ let(:command) { Castle::Commands::Track.build(event: '$login.succeeded', name: "\xC4") }
65
+ let(:expected_body) { { event: '$login.succeeded', name: '�', context: {}, sent_at: time } }
69
66
 
70
- it { expect(build.body).to be_nil }
71
- it { expect(build.method).to eql('GET') }
67
+ before { allow(Castle::Utils::GetTimestamp).to receive(:call).and_return(time) }
68
+
69
+ it { expect(build.body).to be_eql(expected_body.to_json) }
70
+ it { expect(build.method).to eql('POST') }
72
71
  it { expect(build.path).to eql("/v1/#{command.path}") }
73
72
  it { expect(build.to_hash).to have_key('authorization') }
74
73
  it { expect(build.to_hash).to have_key('sample-header') }
75
74
  it { expect(build.to_hash['sample-header']).to eql(['1']) }
76
75
  end
77
76
 
78
- context 'when post' do
77
+ context 'when get' do
79
78
  let(:time) { Time.now.utc.iso8601(3) }
80
- let(:command) do
81
- Castle::Commands::Track.build(event: '$login.succeeded', name: "\xC4")
82
- end
83
- let(:expected_body) do
84
- {
85
- event: '$login.succeeded',
86
- name: '�',
87
- context: {},
88
- sent_at: time
89
- }
90
- end
79
+ let(:command) { Castle::Commands::GetDevice.build(device_token: '1') }
80
+ let(:expected_body) { {} }
91
81
 
92
82
  before { allow(Castle::Utils::GetTimestamp).to receive(:call).and_return(time) }
93
83
 
94
84
  it { expect(build.body).to be_eql(expected_body.to_json) }
95
- it { expect(build.method).to eql('POST') }
85
+ it { expect(build.method).to eql('GET') }
86
+ it { expect(build.path).to eql("/v1/#{command.path}") }
87
+ end
88
+
89
+ context 'when put' do
90
+ let(:time) { Time.now.utc.iso8601(3) }
91
+ let(:command) { Castle::Commands::ApproveDevice.build(device_token: '1') }
92
+ let(:expected_body) { {} }
93
+
94
+ before { allow(Castle::Utils::GetTimestamp).to receive(:call).and_return(time) }
95
+
96
+ it { expect(build.body).to be_eql(expected_body.to_json) }
97
+ it { expect(build.method).to eql('PUT') }
96
98
  it { expect(build.path).to eql("/v1/#{command.path}") }
97
- it { expect(build.to_hash).to have_key('authorization') }
98
- it { expect(build.to_hash).to have_key('sample-header') }
99
- it { expect(build.to_hash['sample-header']).to eql(['1']) }
100
99
  end
101
100
  end
102
101
  end
@@ -98,8 +98,6 @@ describe Castle::Headers::Extract do
98
98
  Castle.config.denylisted = %w[Accept]
99
99
  end
100
100
 
101
- it do
102
- expect(extract_call['Accept']).to eq(true)
103
- end
101
+ it { expect(extract_call['Accept']).to eq(true) }
104
102
  end
105
103
  end
@@ -4,17 +4,18 @@ describe Castle::Headers::Filter do
4
4
  subject(:filter_call) { described_class.new(request).call }
5
5
 
6
6
  let(:env) do
7
- result = Rack::MockRequest.env_for(
8
- '/',
9
- 'Action-Dispatch.request.content-Type' => 'application/json',
10
- 'HTTP_AUTHORIZATION' => 'Basic 123456',
11
- 'HTTP_COOKIE' => '__cid=abcd;other=efgh',
12
- 'HTTP_ACCEPT' => 'application/json',
13
- 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
14
- 'HTTP_USER_AGENT' => 'Mozilla 1234',
15
- 'TEST' => '1',
16
- 'REMOTE_ADDR' => '1.2.3.4'
17
- )
7
+ result =
8
+ Rack::MockRequest.env_for(
9
+ '/',
10
+ 'Action-Dispatch.request.content-Type' => 'application/json',
11
+ 'HTTP_AUTHORIZATION' => 'Basic 123456',
12
+ 'HTTP_COOKIE' => '__cid=abcd;other=efgh',
13
+ 'HTTP_ACCEPT' => 'application/json',
14
+ 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
15
+ 'HTTP_USER_AGENT' => 'Mozilla 1234',
16
+ 'TEST' => '1',
17
+ 'REMOTE_ADDR' => '1.2.3.4'
18
+ )
18
19
  result[:HTTP_OK] = 'OK'
19
20
  result
20
21
  end
@@ -43,9 +43,7 @@ describe Castle::IPs::Extract do
43
43
  end
44
44
 
45
45
  context 'with all the trusted proxies' do
46
- let(:http_x_header) do
47
- '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1'
48
- end
46
+ let(:http_x_header) { '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1' }
49
47
 
50
48
  let(:headers) { { 'Remote-Addr' => '127.0.0.1', 'X-Forwarded-For' => http_x_header } }
51
49
 
@@ -55,9 +53,7 @@ describe Castle::IPs::Extract do
55
53
  end
56
54
 
57
55
  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
56
+ let(:http_x_header) { '6.6.6.6, 2.2.2.3, 6.6.6.5' }
61
57
 
62
58
  let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
63
59
 
@@ -69,9 +65,7 @@ describe Castle::IPs::Extract do
69
65
  end
70
66
 
71
67
  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
68
+ let(:http_x_header) { '6.6.6.6, 2.2.2.3, 6.6.6.5' }
75
69
 
76
70
  let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
77
71
 
@@ -84,10 +78,7 @@ describe Castle::IPs::Extract do
84
78
 
85
79
  context 'when list of not trusted ips provided in X_FORWARDED_FOR' do
86
80
  let(:headers) do
87
- {
88
- 'X-Forwarded-For' => '6.6.6.6, 2.2.2.3, 192.168.0.7',
89
- 'Client-Ip' => '6.6.6.6'
90
- }
81
+ { 'X-Forwarded-For' => '6.6.6.6, 2.2.2.3, 192.168.0.7', 'Client-Ip' => '6.6.6.6' }
91
82
  end
92
83
 
93
84
  it 'does not allow to spoof ip' do
@@ -7,18 +7,14 @@ class TmpLogger
7
7
  end
8
8
 
9
9
  describe Castle::Logger do
10
- subject(:log) do
11
- described_class.call(message, data)
12
- end
10
+ subject(:log) { described_class.call(message, data) }
13
11
 
14
12
  let(:message) { 'https://localhost/test:' }
15
13
  let(:integration_logger) { TmpLogger.new }
16
14
  let(:data) { { a: 1 }.to_json }
17
15
  let(:logger_message) { "[CASTLE] #{message} #{data}" }
18
16
 
19
- before do
20
- allow(integration_logger).to receive(:info).and_call_original
21
- end
17
+ before { allow(integration_logger).to receive(:info).and_call_original }
22
18
 
23
19
  describe '.call' do
24
20
  context 'without logger' do
@@ -15,9 +15,7 @@ describe Castle::Payload::Prepare do
15
15
  let(:request) { Rack::Request.new(env) }
16
16
 
17
17
  let(:headers) do
18
- {
19
- 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true
20
- }
18
+ { 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true }
21
19
  end
22
20
  let(:context) do
23
21
  {
@@ -26,7 +24,10 @@ describe Castle::Payload::Prepare do
26
24
  user_agent: ua,
27
25
  headers: headers,
28
26
  ip: ip,
29
- library: { name: 'castle-rb', version: '2.2.0' }
27
+ library: {
28
+ name: 'castle-rb',
29
+ version: '2.2.0'
30
+ }
30
31
  }
31
32
  end
32
33
 
@@ -16,35 +16,20 @@ describe Castle::Session do
16
16
  let(:request) { Net::HTTP::Get.new(api_url) }
17
17
 
18
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
19
+ allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original
28
20
 
29
- it do
30
- expect(Net::HTTP)
31
- .to have_received(:new)
32
- .with(localhost, port)
21
+ described_class.call { |http| http.request(request) }
33
22
  end
34
23
 
35
- it do
36
- expect(a_request(:get, 'localhost:3002/test'))
37
- .to have_been_made.once
38
- end
24
+ it { expect(Net::HTTP).to have_received(:new).with(localhost, port) }
25
+
26
+ it { expect(a_request(:get, 'localhost:3002/test')).to have_been_made.once }
39
27
  end
40
28
 
41
29
  context 'without block' do
42
30
  before { described_class.call }
43
31
 
44
- it do
45
- expect(a_request(:get, 'localhost:3002/test'))
46
- .not_to have_been_made
47
- end
32
+ it { expect(a_request(:get, 'localhost:3002/test')).not_to have_been_made }
48
33
  end
49
34
  end
50
35
 
@@ -55,7 +40,9 @@ describe Castle::Session do
55
40
  before do
56
41
  Castle.config.base_url = 'https://localhost'
57
42
  stub_request(:get, 'https://localhost/test').to_return(
58
- status: 200, body: '{}', headers: {}
43
+ status: 200,
44
+ body: '{}',
45
+ headers: {}
59
46
  )
60
47
  end
61
48
 
@@ -64,24 +51,14 @@ describe Castle::Session do
64
51
  let(:request) { Net::HTTP::Get.new(api_url) }
65
52
 
66
53
  before do
67
- allow(Net::HTTP)
68
- .to receive(:new)
69
- .with(localhost, port)
70
- .and_call_original
54
+ allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original
71
55
 
72
- allow(Net::HTTP)
73
- .to receive(:start)
56
+ allow(Net::HTTP).to receive(:start)
74
57
 
75
- described_class.call do |http|
76
- http.request(request)
77
- end
58
+ described_class.call { |http| http.request(request) }
78
59
  end
79
60
 
80
- it do
81
- expect(Net::HTTP)
82
- .to have_received(:new)
83
- .with(localhost, port)
84
- end
61
+ it { expect(Net::HTTP).to have_received(:new).with(localhost, port) }
85
62
  end
86
63
  end
87
64
  end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::SingletonConfiguration do
4
- subject(:config) do
5
- described_class.instance
6
- end
4
+ subject(:config) { described_class.instance }
7
5
 
8
6
  it_behaves_like 'configuration_host'
9
7
  it_behaves_like 'configuration_request_timeout'
@@ -12,7 +10,5 @@ describe Castle::SingletonConfiguration do
12
10
  it_behaves_like 'configuration_failover_strategy'
13
11
  it_behaves_like 'configuration_api_secret'
14
12
 
15
- it do
16
- expect(config.api_secret).to be_eql('secret')
17
- end
13
+ it { expect(config.api_secret).to be_eql('secret') }
18
14
  end