wavefront-sdk 1.2.1 → 1.3.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.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +62 -72
  4. data/.travis.yml +5 -4
  5. data/README.md +10 -0
  6. data/lib/wavefront-sdk/base.rb +28 -8
  7. data/lib/wavefront-sdk/constants.rb +3 -0
  8. data/lib/wavefront-sdk/credentials.rb +36 -17
  9. data/lib/wavefront-sdk/mixins.rb +3 -22
  10. data/lib/wavefront-sdk/notificant.rb +1 -1
  11. data/lib/wavefront-sdk/parse_time.rb +58 -0
  12. data/lib/wavefront-sdk/response.rb +7 -6
  13. data/lib/wavefront-sdk/search.rb +5 -2
  14. data/lib/wavefront-sdk/user.rb +7 -0
  15. data/lib/wavefront-sdk/validators.rb +15 -7
  16. data/lib/wavefront-sdk/version.rb +1 -1
  17. data/lib/wavefront-sdk/write.rb +29 -3
  18. data/spec/.rubocop.yml +3 -0
  19. data/spec/spec_helper.rb +24 -18
  20. data/spec/wavefront-sdk/alert_spec.rb +5 -0
  21. data/spec/wavefront-sdk/base_spec.rb +9 -9
  22. data/spec/wavefront-sdk/credentials_spec.rb +123 -4
  23. data/spec/wavefront-sdk/event_spec.rb +4 -5
  24. data/spec/wavefront-sdk/externallink_spec.rb +1 -1
  25. data/spec/wavefront-sdk/maintenancewindow_spec.rb +2 -2
  26. data/spec/wavefront-sdk/metric_spec.rb +2 -2
  27. data/spec/wavefront-sdk/mixins_spec.rb +1 -1
  28. data/spec/wavefront-sdk/parse_time_spec.rb +65 -0
  29. data/spec/wavefront-sdk/resources/test2.conf +4 -0
  30. data/spec/wavefront-sdk/response_spec.rb +3 -5
  31. data/spec/wavefront-sdk/savedsearch_spec.rb +1 -1
  32. data/spec/wavefront-sdk/source_spec.rb +1 -1
  33. data/spec/wavefront-sdk/user_spec.rb +5 -3
  34. data/spec/wavefront-sdk/validators_spec.rb +65 -62
  35. data/spec/wavefront-sdk/webhook_spec.rb +1 -1
  36. data/spec/wavefront-sdk/write_spec.rb +19 -1
  37. data/wavefront-sdk.gemspec +4 -3
  38. metadata +29 -15
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.2
3
+ #
1
4
  #
2
5
  # Allow long blocks of tests
3
6
  #
@@ -1,6 +1,11 @@
1
1
  #
2
2
  # Stuff needed by multiple tests
3
3
  #
4
+
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ add_filter '/spec/'
8
+ end
4
9
  require 'minitest/autorun'
5
10
  require 'webmock/minitest'
6
11
 
@@ -10,17 +15,18 @@ CREDS = {
10
15
  }
11
16
 
12
17
  POST_HEADERS = {
13
- :'Content-Type' => 'text/plain', :Accept => 'application/json'
18
+ 'Content-Type': 'text/plain', Accept: 'application/json'
14
19
  }.freeze
15
20
 
16
21
  JSON_POST_HEADERS = {
17
- :'Content-Type' => 'application/json', :Accept => 'application/json'
22
+ 'Content-Type': 'application/json', Accept: 'application/json'
18
23
  }.freeze
19
24
 
20
25
  DUMMY_RESPONSE = '{"status":{"result":"OK","message":"","code":200},' \
21
26
  '"response":{"items":[{"name":"test data"}],"offset":0,' \
22
- '"limit":100,"totalItems":3,"moreItems":false}}'
27
+ '"limit":100,"totalItems":3,"moreItems":false}}'.freeze
23
28
 
29
+ # Common testing code
24
30
  class WavefrontTestBase < MiniTest::Test
25
31
  attr_reader :wf, :wf_noop, :uri_base, :headers
26
32
 
@@ -60,21 +66,22 @@ class WavefrontTestBase < MiniTest::Test
60
66
  # @body [String] a JSON object you expect to be sent as part of
61
67
  # the request
62
68
  #
63
- #
69
+ # rubocop:disable Metrics/PerceivedComplexity
70
+ # rubocop:disable Metrics/ParameterLists
64
71
  def should_work(method, args, path, call = :get, more_headers = {},
65
72
  body = nil, id = nil)
66
73
  path = Array(path)
67
- uri = target_uri(path.first).sub(/\/$/, '')
74
+ uri = target_uri(path.first).sub(%r{/$}, '')
68
75
 
69
76
  headers = { 'Accept': /.*/,
70
77
  'Accept-Encoding': /.*/,
71
78
  'Authorization': 'Bearer 0123456789-ABCDEF',
72
- 'User-Agent': "wavefront-sdk #{WF_SDK_VERSION}"
73
- }.merge(more_headers)
79
+ 'User-Agent': "wavefront-sdk #{WF_SDK_VERSION}" }
80
+ .merge(more_headers)
74
81
 
75
82
  if body
76
- stub_request(call, uri).with(body: body, headers:headers)
77
- .to_return(body: DUMMY_RESPONSE, status: 200)
83
+ stub_request(call, uri).with(body: body, headers: headers)
84
+ .to_return(body: DUMMY_RESPONSE, status: 200)
78
85
  else
79
86
  stub_request(call, uri).to_return(body: DUMMY_RESPONSE, status: 200)
80
87
  end
@@ -85,12 +92,10 @@ class WavefrontTestBase < MiniTest::Test
85
92
  else
86
93
  wf.send(method, args)
87
94
  end
95
+ elsif id
96
+ wf.send(method, id, *args)
88
97
  else
89
- if id
90
- wf.send(method, id, *args)
91
- else
92
- wf.send(method, *args)
93
- end
98
+ wf.send(method, *args)
94
99
  end
95
100
 
96
101
  assert_requested(call, uri, headers: headers)
@@ -99,7 +104,7 @@ class WavefrontTestBase < MiniTest::Test
99
104
 
100
105
  def standard_exception
101
106
  Object.const_get('Wavefront::Exception')
102
- .const_get("Invalid#{class_basename}Id")
107
+ .const_get("Invalid#{class_basename}Id")
103
108
  end
104
109
 
105
110
  def should_be_invalid(method, args = '!!invalid_val!!')
@@ -118,8 +123,8 @@ class WavefrontTestBase < MiniTest::Test
118
123
  #
119
124
  should_work('tag_set', [id, 'tag'],
120
125
  ["#{id}/tag", ['tag'].to_json], :post, JSON_POST_HEADERS)
121
- should_work('tag_set', [id, %w(tag1 tag2)],
122
- ["#{id}/tag", %w(tag1 tag2).to_json], :post,
126
+ should_work('tag_set', [id, %w[tag1 tag2]],
127
+ ["#{id}/tag", %w[tag1 tag2].to_json], :post,
123
128
  JSON_POST_HEADERS)
124
129
  should_fail_tags('tag_set', id)
125
130
 
@@ -146,8 +151,9 @@ class WavefrontTestBase < MiniTest::Test
146
151
  end
147
152
  end
148
153
 
154
+ # Extensions to stdlib
155
+ #
149
156
  class Hash
150
-
151
157
  # A quick way to deep-copy a hash.
152
158
  #
153
159
  def dup
@@ -20,6 +20,11 @@ class WavefrontAlertTest < WavefrontTestBase
20
20
  should_work(:list, 10, '?offset=10&limit=100')
21
21
  end
22
22
 
23
+ def test_update_keys
24
+ assert_instance_of(Array, wf.update_keys)
25
+ wf.update_keys.each { |k| assert_instance_of(Symbol, k) }
26
+ end
27
+
23
28
  def test_describe
24
29
  should_work(:describe, ALERT, ALERT)
25
30
  assert_raises(ArgumentError) { wf.describe }
@@ -20,17 +20,17 @@ class WavefrontBaseTest < MiniTest::Test
20
20
  def test_time_to_ms
21
21
  now_ms = Time.now.to_i * 1000
22
22
  assert_equal wf.time_to_ms(now_ms), now_ms
23
- assert_equal wf.time_to_ms(1469711187), 1469711187000
23
+ assert_equal wf.time_to_ms(1_469_711_187), 1_469_711_187_000
24
24
  refute wf.time_to_ms([])
25
25
  refute wf.time_to_ms('1469711187')
26
26
  end
27
27
 
28
28
  def test_uri_concat
29
- assert_equal %w(a b).uri_concat, 'a/b'
29
+ assert_equal %w[a b].uri_concat, 'a/b'
30
30
  assert_equal ['', 'a', 'b'].uri_concat, '/a/b'
31
- assert_equal %w(a /b).uri_concat, 'a/b'
31
+ assert_equal %w[a /b].uri_concat, 'a/b'
32
32
  assert_equal ['', 'a', 'b/'].uri_concat, '/a/b'
33
- assert_equal %w(/a /b/ /c).uri_concat, '/a/b/c'
33
+ assert_equal %w[/a /b/ /c].uri_concat, '/a/b/c'
34
34
  assert_equal ['/a', '/b c'].uri_concat, '/a/b c'
35
35
  end
36
36
 
@@ -43,7 +43,7 @@ class WavefrontBaseTest < MiniTest::Test
43
43
 
44
44
  def test_api_post
45
45
  uri = "#{uri_base}/path"
46
- obj = {key: 'value'}
46
+ obj = { key: 'value' }
47
47
  stub_request(:post, uri).to_return(body: DUMMY_RESPONSE, status: 200)
48
48
  wf.api_post('/path', 'string')
49
49
  assert_requested(:post, uri, body: 'string',
@@ -71,7 +71,7 @@ class WavefrontBaseTest < MiniTest::Test
71
71
  def test_api_noop
72
72
  uri = "#{uri_base}/path"
73
73
 
74
- %w(get post put delete).each do |call|
74
+ %w[get post put delete].each do |call|
75
75
  stub_request(call.to_sym, uri)
76
76
  wf_noop.send("api_#{call}", '/path')
77
77
  refute_requested(call.to_sym, uri)
@@ -79,10 +79,10 @@ class WavefrontBaseTest < MiniTest::Test
79
79
  end
80
80
 
81
81
  def test_hash_for_update
82
- wf.instance_variable_set('@update_keys', [:k1, :k2])
83
- body = { k1: 'ov1', k2: 'ov2', k3: 'ov3'}
82
+ wf.instance_variable_set('@update_keys', %i[k1 k2])
83
+ body = { k1: 'ov1', k2: 'ov2', k3: 'ov3' }
84
84
  upd = { k2: 'nv1' }
85
85
 
86
- assert_equal(wf.hash_for_update(body, upd), { k1: 'ov1', k2: 'nv1'})
86
+ assert_equal(wf.hash_for_update(body, upd), k1: 'ov1', k2: 'nv1')
87
87
  end
88
88
  end
@@ -5,8 +5,9 @@ require_relative '../spec_helper'
5
5
  require_relative '../../lib/wavefront-sdk/credentials'
6
6
 
7
7
  CONF = Pathname.new(__FILE__).dirname.realpath + 'resources' + 'test.conf'
8
+ CONF2 = Pathname.new(__FILE__).dirname.realpath + 'resources' + 'test2.conf'
8
9
 
9
- # Test SDK base class
10
+ # Test SDK base class end-to-end
10
11
  #
11
12
  class WavefrontCredentialsTest < MiniTest::Test
12
13
  attr_reader :wf, :wf_noop, :uri_base, :headers
@@ -20,7 +21,7 @@ class WavefrontCredentialsTest < MiniTest::Test
20
21
  assert_instance_of(Map, c.proxy)
21
22
  assert_instance_of(Map, c.config)
22
23
 
23
- assert_equal(c.creds.keys, %w(token endpoint))
24
+ assert_equal(c.creds.keys, %w[token endpoint])
24
25
  assert_equal(c.creds[:token], '12345678-abcd-1234-abcd-123456789012')
25
26
  assert_equal(c.creds[:endpoint], 'default.wavefront.com')
26
27
  end
@@ -34,7 +35,7 @@ class WavefrontCredentialsTest < MiniTest::Test
34
35
  assert_instance_of(Map, c.proxy)
35
36
  assert_instance_of(Map, c.config)
36
37
 
37
- assert_equal(c.creds.keys, %w(token endpoint))
38
+ assert_equal(c.creds.keys, %w[token endpoint])
38
39
  assert_equal(c.creds[:token], 'abcdefgh')
39
40
  assert_equal(c.creds[:endpoint], 'default.wavefront.com')
40
41
  end
@@ -48,8 +49,126 @@ class WavefrontCredentialsTest < MiniTest::Test
48
49
  assert_instance_of(Map, c.proxy)
49
50
  assert_instance_of(Map, c.config)
50
51
 
51
- assert_equal(c.creds.keys, %w(token endpoint))
52
+ assert_equal(c.creds.keys, %w[token endpoint])
52
53
  assert_equal(c.creds[:token], '12345678-abcd-1234-abcd-123456789012')
53
54
  assert_equal(c.creds[:endpoint], 'endpoint.wavefront.com')
54
55
  end
56
+
57
+ end
58
+
59
+ # Test individual methods. We must override the constructor to do
60
+ # this.
61
+ #
62
+ class Giblets < Wavefront::Credentials
63
+ def initialize; end
64
+ end
65
+
66
+ class GibletsTest < MiniTest::Test
67
+ attr_reader :wf, :raw
68
+
69
+ def setup
70
+ @wf = Giblets.new
71
+ @raw = { endpoint: 'raw_ep', token: 'raw_tok', proxy: 'raw_proxy' }
72
+ clear_env_vars
73
+ end
74
+
75
+ def clear_env_vars
76
+ %w[WAVEFRONT_ENDPOINT WAVEFRONT_TOKEN WAVEFRONT_PROXY].each do |ev|
77
+ ENV.delete(ev)
78
+ end
79
+ end
80
+
81
+ def test_env_override_noenvs
82
+ assert_equal(wf.env_override(raw), raw)
83
+ end
84
+
85
+ def test_env_override_env_endpoint
86
+ ENV['WAVEFRONT_ENDPOINT'] = 'env_ep'
87
+
88
+ assert_equal(wf.env_override(raw),
89
+ { endpoint: 'env_ep', token: 'raw_tok', proxy: 'raw_proxy' })
90
+ end
91
+
92
+ def test_env_override_env_endpoint_and_token
93
+ ENV['WAVEFRONT_ENDPOINT'] = 'env_ep'
94
+ ENV['WAVEFRONT_TOKEN'] = 'env_tok'
95
+
96
+ assert_equal(wf.env_override(raw),
97
+ { endpoint: 'env_ep', token: 'env_tok', proxy: 'raw_proxy' })
98
+ end
99
+
100
+ def test_env_override_env_proxy
101
+ ENV['WAVEFRONT_PROXY'] = 'env_proxy'
102
+ x = wf.env_override(raw)
103
+
104
+ assert_instance_of(Hash, x)
105
+ assert_equal(x, { endpoint: 'raw_ep', token: 'raw_tok', proxy:
106
+ 'env_proxy' })
107
+ end
108
+
109
+ def test_populate
110
+ wf.populate(raw)
111
+ config = wf.instance_variable_get('@config')
112
+ creds = wf.instance_variable_get('@creds')
113
+ proxy = wf.instance_variable_get('@proxy')
114
+
115
+ assert_instance_of(Map, config)
116
+ assert_equal(config.proxy, 'raw_proxy')
117
+ assert_equal(config.endpoint, 'raw_ep')
118
+
119
+ assert_instance_of(Map, creds)
120
+ assert_equal(creds.endpoint, 'raw_ep')
121
+ assert_equal(creds.token, 'raw_tok')
122
+ refute creds[:proxy]
123
+
124
+ assert_instance_of(Map, proxy)
125
+ assert_equal(config.proxy, 'raw_proxy')
126
+ refute proxy[:endpoint]
127
+ end
128
+
129
+ def test_cred_files_no_opts
130
+ x = wf.cred_files
131
+ assert_instance_of(Array, x)
132
+ assert_equal(x.length, 2)
133
+ x.each { |p| assert_instance_of(Pathname, p) }
134
+ assert_includes(x, Pathname.new('/etc/wavefront/credentials'))
135
+ assert_includes(x, Pathname.new(ENV['HOME']) + '.wavefront')
136
+ end
137
+
138
+ def test_cred_files_opts
139
+ x = wf.cred_files(file: '/test/file')
140
+ assert_instance_of(Array, x)
141
+ assert_equal(x.length, 1)
142
+ assert_equal(x, Array(Pathname.new('/test/file')))
143
+ end
144
+
145
+ def test_load_from_file
146
+ assert_equal(wf.load_from_file(
147
+ [Pathname.new('/no/file/1'), Pathname.new('/no/file/2')]), {})
148
+
149
+ assert_equal(wf.load_from_file([CONF], 'noprofile'),
150
+ { file: CONF })
151
+
152
+ x = wf.load_from_file([CONF2, CONF], 'default')
153
+ assert_instance_of(Hash, x)
154
+ assert_equal(x.keys.size, 5)
155
+ assert_equal(x[:proxy], 'wavefront.localnet')
156
+
157
+ %i[token endpoint proxy sourceformat file].each do |k|
158
+ assert_includes(x.keys, k)
159
+ end
160
+
161
+ y = wf.load_from_file([CONF2, CONF], 'other')
162
+ assert_instance_of(Hash, y)
163
+ %i[token endpoint proxy file].each { |k| assert_includes(y.keys, k) }
164
+ assert_equal(y.keys.size, 4)
165
+ assert_equal(y[:proxy], 'otherwf.localnet')
166
+
167
+ z = wf.load_from_file([CONF, CONF2], 'default')
168
+ assert_instance_of(Hash, z)
169
+ %i[token endpoint proxy file].each { |k| assert_includes(z.keys, k) }
170
+ assert_equal(z.keys.size, 4)
171
+ assert_equal(z[:proxy], 'wavefront.lab')
172
+ assert_equal(z[:endpoint], 'somewhere.wavefront.com')
173
+ end
55
174
  end
@@ -10,10 +10,10 @@ EVENT_BODY = {
10
10
  type: 'SDK test event',
11
11
  details: 'an imaginary event to test the SDK'
12
12
  },
13
- hosts: %w(host1 host2),
14
- startTime: 1493385089000,
15
- endTime: 1493385345678,
16
- tags: %w(tag1 tag2),
13
+ hosts: %w[host1 host2],
14
+ startTime: 1_493_385_089_000,
15
+ endTime: 1_493_385_345_678,
16
+ tags: %w[tag1 tag2],
17
17
  isEphemeral: false
18
18
  }.freeze
19
19
 
@@ -21,7 +21,6 @@ EVENT_BODY = {
21
21
  #
22
22
  class WavefrontEventTest < WavefrontTestBase
23
23
  def test_list
24
-
25
24
  t1 = Time.now - 600
26
25
  t2 = Time.now
27
26
  tms1 = t1.to_datetime.strftime('%Q')
@@ -7,7 +7,7 @@ EXTERNAL_LINK_BODY = {
7
7
  name: 'test link',
8
8
  template: 'https://example.com/link/{{value}}',
9
9
  description: 'an imaginary link for unit testing purposes'
10
- }
10
+ }.freeze
11
11
 
12
12
  EXTERNAL_LINK_BODY_2 = {
13
13
  name: 'test link',
@@ -8,8 +8,8 @@ WINDOW_BODY = {
8
8
  title: 'test window',
9
9
  start: Time.now.to_i,
10
10
  end: Time.now.to_i + 600,
11
- tags: %w(testtag1 testtag2),
12
- hostTags: %w(hosttag1 hosttag2)
11
+ tags: %w[testtag1 testtag2],
12
+ hostTags: %w[hosttag1 hosttag2]
13
13
  }.freeze
14
14
 
15
15
  # Unit tests for MaintenanceWindow class
@@ -11,9 +11,9 @@ class WavefrontMetricTest < WavefrontTestBase
11
11
 
12
12
  def test_detail
13
13
  should_work(:detail, 'metric.1', 'detail?m=metric.1')
14
- should_work(:detail, ['metric.1', %w(host1 host2)],
14
+ should_work(:detail, ['metric.1', %w[host1 host2]],
15
15
  'detail?m=metric.1&h=host1&h=host2')
16
- should_work(:detail, ['metric.1', %w(host1 host2), 'abc'],
16
+ should_work(:detail, ['metric.1', %w[host1 host2], 'abc'],
17
17
  'detail?m=metric.1&h=host1&h=host2&c=abc')
18
18
  assert_raises(ArgumentError) { wf.detail }
19
19
  assert_raises(ArgumentError) { wf.detail('m1', 'm2') }
@@ -57,7 +57,7 @@ class WavefrontMixinsTest < MiniTest::Test
57
57
  def test_parse_relative_time
58
58
  assert_equal parse_relative_time('-5s'), -5
59
59
  assert_equal parse_relative_time('-5s', true), -5000
60
- assert_equal parse_relative_time('+10000s'), 10000
60
+ assert_equal parse_relative_time('+10000s'), 10_000
61
61
  assert_equal parse_relative_time('-5m'), -300
62
62
  assert_equal parse_relative_time('-.5m'), -30
63
63
  assert_equal parse_relative_time('-0.5m'), -30
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../spec_helper'
4
+ require_relative '../../lib/wavefront-sdk/parse_time'
5
+
6
+ TSS = 1517151869
7
+ TSM = 1517151869523
8
+
9
+ # Test parse_time class
10
+ #
11
+ class WavefrontParseTimeTest < MiniTest::Test
12
+ attr_reader :pts, :ptm
13
+
14
+ def setup
15
+ @pts = Wavefront::ParseTime.new(TSS, false)
16
+ @ptm = Wavefront::ParseTime.new(TSM, true)
17
+ end
18
+
19
+ def test_parse_time_Fixnum
20
+ assert_equal(pts.parse_time_Fixnum, TSS)
21
+ assert_equal(ptm.parse_time_Fixnum, TSM)
22
+ end
23
+
24
+ def test_parse_time_Integer
25
+ assert_equal(pts.parse_time_Integer, TSS)
26
+ assert_equal(ptm.parse_time_Integer, TSM)
27
+ end
28
+
29
+ def test_parse_time_String
30
+ ptss = Wavefront::ParseTime.new(TSS.to_s, false)
31
+ ptms = Wavefront::ParseTime.new(TSM.to_s, true)
32
+ assert_instance_of(Fixnum, ptss.parse_time_String, TSS)
33
+ assert_instance_of(Fixnum, ptms.parse_time_String, TSM)
34
+ assert_equal(ptss.parse_time_String, TSS)
35
+ assert_equal(ptms.parse_time_String, TSM)
36
+ assert_instance_of(Fixnum, ptss.parse!)
37
+ assert_instance_of(Fixnum, ptms.parse!)
38
+ end
39
+
40
+ def test_parse_time_Time
41
+ ptst = Wavefront::ParseTime.new(Time.at(TSS), false)
42
+ ptmt = Wavefront::ParseTime.new(DateTime.strptime(TSM.to_s,
43
+ '%Q').to_time, true)
44
+ assert_equal(ptst.parse_time_Time, TSS)
45
+ assert_equal(ptmt.parse_time_Time, TSM)
46
+ assert_instance_of(Fixnum, ptst.parse!)
47
+ assert_instance_of(Fixnum, ptmt.parse!)
48
+ end
49
+
50
+ def test_parse_time_DateTime
51
+ ptsd = Wavefront::ParseTime.new(Time.at(TSS).to_datetime, false)
52
+ ptmd = Wavefront::ParseTime.new(DateTime.strptime(TSM.to_s, '%Q'), true)
53
+ assert_instance_of(Fixnum, ptsd.parse_time_DateTime, TSS)
54
+ assert_instance_of(Fixnum, ptmd.parse_time_DateTime, TSM)
55
+ assert_equal(ptsd.parse_time_DateTime, TSS)
56
+ assert_equal(ptmd.parse_time_DateTime, TSM)
57
+ assert_instance_of(Fixnum, ptsd.parse!)
58
+ assert_instance_of(Fixnum, ptmd.parse!)
59
+ end
60
+
61
+ def test_parse!
62
+ assert_instance_of(Fixnum, pts.parse!)
63
+ assert_instance_of(Fixnum, ptm.parse!)
64
+ end
65
+ end