radiator 0.4.7 → 0.4.8.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -7
  3. data/Rakefile +50 -20
  4. data/lib/radiator.rb +3 -1
  5. data/lib/radiator/api.rb +83 -10
  6. data/lib/radiator/bridge.rb +34 -0
  7. data/lib/radiator/chain_config.rb +9 -2
  8. data/lib/radiator/database_api.rb +1 -1
  9. data/lib/radiator/follow_api.rb +1 -1
  10. data/lib/radiator/market_history_api.rb +1 -1
  11. data/lib/radiator/operation.rb +3 -2
  12. data/lib/radiator/operation_types.rb +43 -27
  13. data/lib/radiator/ssc/base_steem_smart_contract_rpc.rb +1 -1
  14. data/lib/radiator/stream.rb +15 -6
  15. data/lib/radiator/transaction.rb +28 -1
  16. data/lib/radiator/type/amount.rb +26 -51
  17. data/lib/radiator/type/price.rb +2 -2
  18. data/lib/radiator/version.rb +1 -1
  19. data/radiator.gemspec +13 -11
  20. data/test/fixtures/empty.json +1 -0
  21. data/test/fixtures/error.json +29 -0
  22. data/test/fixtures/follow_api_get_followers.json +1 -0
  23. data/test/fixtures/get_account.json +165 -0
  24. data/test/fixtures/get_account_count.json +1 -0
  25. data/test/fixtures/get_account_references.json +1 -0
  26. data/test/fixtures/get_block.json +193 -0
  27. data/test/fixtures/get_dynamic_global_properties.json +32 -0
  28. data/test/fixtures/get_feed_history.json +684 -0
  29. data/test/fixtures/get_hardfork_version.json +1 -0
  30. data/test/fixtures/get_key_references.json +14 -0
  31. data/test/fixtures/get_stats_for_time.json +57 -0
  32. data/test/fixtures/get_vesting_delegation.json +936 -0
  33. data/test/fixtures/golos_get_dynamic_global_properties.json +32 -0
  34. data/test/fixtures/market_history_api_get_market_history_buckets.json +1 -0
  35. data/test/fixtures/market_history_api_get_order_book.json +109 -0
  36. data/test/fixtures/market_history_api_get_recent_trades.json +55 -0
  37. data/test/fixtures/market_history_api_get_ticker.json +11 -0
  38. data/test/fixtures/market_history_api_get_volume.json +1 -0
  39. data/test/fixtures/null.json +1 -0
  40. data/test/fixtures/vcr_cassettes/account_by_key_api_all_methods.yml +525 -0
  41. data/test/fixtures/vcr_cassettes/account_by_key_api_jsonrpc.yml +52 -0
  42. data/test/fixtures/vcr_cassettes/all_methods.yml +18155 -0
  43. data/test/fixtures/vcr_cassettes/api_all_methods.yml +13254 -0
  44. data/test/fixtures/vcr_cassettes/base_per_debt.yml +4946 -0
  45. data/test/fixtures/vcr_cassettes/base_per_mvest.yml +3969 -0
  46. data/test/fixtures/vcr_cassettes/block_time.yml +3322 -0
  47. data/test/fixtures/vcr_cassettes/broadcast_transaction.yml +1186 -0
  48. data/test/fixtures/vcr_cassettes/condenser_all_all_methods.yml +13297 -0
  49. data/test/fixtures/vcr_cassettes/expiration_initialize.yml +3428 -0
  50. data/test/fixtures/vcr_cassettes/find_account.yml +3681 -0
  51. data/test/fixtures/vcr_cassettes/find_block.yml +3589 -0
  52. data/test/fixtures/vcr_cassettes/find_comment.yml +11464 -0
  53. data/test/fixtures/vcr_cassettes/follow_api_jsonrpc.yml +52 -0
  54. data/test/fixtures/vcr_cassettes/get_account_count.yml +575 -0
  55. data/test/fixtures/vcr_cassettes/get_account_references.yml +608 -0
  56. data/test/fixtures/vcr_cassettes/get_accounts.yml +674 -0
  57. data/test/fixtures/vcr_cassettes/get_accounts_no_argument.yml +608 -0
  58. data/test/fixtures/vcr_cassettes/get_dynamic_global_properties.yml +661 -0
  59. data/test/fixtures/vcr_cassettes/get_feed_history.yml +1106 -0
  60. data/test/fixtures/vcr_cassettes/get_hardfork_version.yml +577 -0
  61. data/test/fixtures/vcr_cassettes/get_key_references.yml +987 -0
  62. data/test/fixtures/vcr_cassettes/get_market_history.yml +1043 -0
  63. data/test/fixtures/vcr_cassettes/get_market_history_buckets.yml +1043 -0
  64. data/test/fixtures/vcr_cassettes/get_order_book.yml +1091 -0
  65. data/test/fixtures/vcr_cassettes/get_recent_trades.yml +1043 -0
  66. data/test/fixtures/vcr_cassettes/get_ticker.yml +1047 -0
  67. data/test/fixtures/vcr_cassettes/get_trade_history.yml +1049 -0
  68. data/test/fixtures/vcr_cassettes/get_vesting_delegations.yml +523 -0
  69. data/test/fixtures/vcr_cassettes/get_volume.yml +1051 -0
  70. data/test/fixtures/vcr_cassettes/get_witness_by_account.yml +575 -0
  71. data/test/fixtures/vcr_cassettes/look_up_witnesses.yml +523 -0
  72. data/test/fixtures/vcr_cassettes/market_history_api_all_methods.yml +4373 -0
  73. data/test/fixtures/vcr_cassettes/network_broadcast_api_all_methods.yml +1288 -0
  74. data/test/fixtures/vcr_cassettes/properties.yml +3627 -0
  75. data/test/fixtures/vcr_cassettes/recover_transaction.yml +1099 -0
  76. data/test/fixtures/vcr_cassettes/ssc_blockchain_block_info.yml +92 -0
  77. data/test/fixtures/vcr_cassettes/ssc_blockchain_block_info_invalid.yml +90 -0
  78. data/test/fixtures/vcr_cassettes/ssc_blockchain_latest_block_info.yml +91 -0
  79. data/test/fixtures/vcr_cassettes/ssc_blockchain_transaction_info.yml +92 -0
  80. data/test/fixtures/vcr_cassettes/ssc_contracts_contract.yml +366 -0
  81. data/test/fixtures/vcr_cassettes/ssc_contracts_find.yml +91 -0
  82. data/test/fixtures/vcr_cassettes/ssc_contracts_find_one.yml +89 -0
  83. data/test/fixtures/vcr_cassettes/stream_jsonrpc.yml +8253 -0
  84. data/test/fixtures/vcr_cassettes/transaction_expiration_initialize_nil.yml +3176 -0
  85. data/test/fixtures/vcr_cassettes/transaction_jsonrpc.yml +151 -0
  86. data/test/fixtures/vcr_cassettes/unknown_chain_id.yml +3343 -0
  87. data/test/fixtures/vcr_cassettes/valid_chains.yml +3124 -0
  88. data/test/radiator/account_by_key_api_test.rb +46 -0
  89. data/test/radiator/api_test.rb +135 -0
  90. data/test/radiator/chain_stats_api_test.rb +49 -0
  91. data/test/radiator/chain_test.rb +153 -0
  92. data/test/radiator/condenser_api_test.rb +48 -0
  93. data/test/radiator/follow_api_test.rb +48 -0
  94. data/test/radiator/market_history_api_test.rb +100 -0
  95. data/test/radiator/network_broadcast_api_test.rb +48 -0
  96. data/test/radiator/operation_test.rb +117 -0
  97. data/test/radiator/ssc/blockchain_test.rb +58 -0
  98. data/test/radiator/ssc/contracts_test.rb +65 -0
  99. data/test/radiator/stream_test.rb +48 -0
  100. data/test/radiator/tag_api_test.rb +40 -0
  101. data/test/radiator/transaction_test.rb +755 -0
  102. data/test/test_helper.rb +66 -0
  103. metadata +182 -73
  104. data/.codeclimate.yml +0 -19
  105. data/.gitignore +0 -52
  106. data/.travis.yml +0 -23
  107. data/gource.sh +0 -8
  108. data/images/Anthony Martin.png +0 -0
  109. data/images/Marvin Hofmann.jpg +0 -0
  110. data/images/Marvin Hofmann.png +0 -0
  111. data/lib/steem.rb +0 -17
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+
3
+ module Radiator
4
+ class AccountByKeyApiTest < Radiator::Test
5
+ def setup
6
+ vcr_cassette('account_by_key_api_jsonrpc') do
7
+ @api = Radiator::AccountByKeyApi.new(chain_options)
8
+ end
9
+ end
10
+
11
+ def test_method_missing
12
+ assert_raises NoMethodError do
13
+ @api.bogus
14
+ end
15
+ end
16
+
17
+ def test_all_respond_to
18
+ vcr_cassette('account_by_key_api_all_respond_to') do
19
+ @api.method_names.each do |key|
20
+ assert @api.respond_to?(key), "expect rpc respond to #{key}"
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_all_methods
26
+ vcr_cassette('account_by_key_api_all_methods') do
27
+ @api.method_names.each do |key|
28
+ begin
29
+ assert @api.send key
30
+ rescue Steem::ArgumentError => e
31
+ # next
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def test_get_key_references
38
+ vcr_cassette('get_key_references') do
39
+ keys = ['STM71f6yWztimJuREVyyMXNqAVbx1FzPVW6LLXNoQ35dHwKuszmHX']
40
+ @api.get_key_references(keys: keys) do |account_names|
41
+ assert_equal Hashie::Mash, account_names.class, account_names.inspect
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,135 @@
1
+ require 'test_helper'
2
+
3
+ module Radiator
4
+ class ApiTest < Radiator::Test
5
+ def setup
6
+ vcr_cassette('api_jsonrpc') do
7
+ @api = Radiator::Api.new(chain_options.merge(logger: LOGGER))
8
+ end
9
+ end
10
+
11
+ def test_hashie_logger
12
+ assert Radiator::Api.new(chain_options.merge(hashie_logger: 'hashie.log'))
13
+ end
14
+
15
+ def test_method_missing
16
+ assert_raises NoMethodError do
17
+ @api.bogus
18
+ end
19
+ end
20
+
21
+ def test_all_respond_to
22
+ vcr_cassette('api_all_respond_to') do
23
+ @api.method_names.each do |key|
24
+ assert @api.respond_to?(key), "expect rpc respond to #{key}"
25
+ end
26
+ end
27
+ end
28
+
29
+ def test_all_methods
30
+ vcr_cassette('api_all_methods') do
31
+ @api.method_names.each do |key|
32
+ begin
33
+ assert @api.send key
34
+ rescue Steem::ArgumentError => e
35
+ # next
36
+ rescue Steem::RemoteNodeError => e
37
+ # next
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def test_get_accounts_no_argument
44
+ vcr_cassette('get_accounts_no_argument') do
45
+ assert_raises Steem::ArgumentError do
46
+ @api.get_accounts
47
+ end
48
+ end
49
+ end
50
+
51
+ def test_get_accounts
52
+ vcr_cassette('get_accounts') do
53
+ @api.get_accounts(['inertia']) do |accounts|
54
+ assert_equal Hashie::Array, accounts.class, accounts.inspect
55
+ account = accounts.first
56
+ owner_key_auths = account.owner.key_auths.first
57
+ assert_equal 'STM6qpwgqwzaF8E1GsKh28E8HVRzbBdewcimKzLmn1Rjgq7SQoNUa', owner_key_auths.first
58
+ end
59
+ end
60
+ end
61
+
62
+ def test_get_feed_history
63
+ vcr_cassette('get_feed_history') do
64
+ @api.get_feed_history() do |history|
65
+ assert_equal Hashie::Mash, history.class, history.inspect
66
+ end
67
+ end
68
+ end
69
+
70
+ def test_get_account_count
71
+ vcr_cassette('get_account_count') do
72
+ @api.get_account_count do |count|
73
+ skip "Fixnum is deprecated." if count.class.to_s == 'Fixnum'
74
+ assert_equal Integer, count.class, count.inspect
75
+ end
76
+ end
77
+ end
78
+
79
+ def test_get_account_references
80
+ vcr_cassette('get_account_references') do
81
+ begin
82
+ @api.get_account_references(["2.2.27007"]) do |_, error|
83
+ assert_equal Hashie::Mash, error.class, error.inspect
84
+ end
85
+ rescue Steem::UnknownError => e
86
+ raise e unless e.inspect.include? 'condenser_api::get_account_references --- Needs to be refactored for Steem'
87
+
88
+ # next
89
+ end
90
+ end
91
+ end
92
+
93
+ def test_get_dynamic_global_properties
94
+ vcr_cassette('get_dynamic_global_properties') do
95
+ @api.get_dynamic_global_properties do |properties|
96
+ assert_equal Hashie::Mash, properties.class, properties.inspect
97
+ end
98
+ end
99
+ end
100
+
101
+ def test_get_hardfork_version
102
+ vcr_cassette('get_hardfork_version') do
103
+ @api.get_hardfork_version do |version|
104
+ assert_equal String, version.class, version.inspect
105
+ end
106
+ end
107
+ end
108
+
109
+ def test_get_vesting_delegations
110
+ vcr_cassette('get_vesting_delegations') do
111
+ @api.get_vesting_delegations('minnowbooster', -1000, 1000) do |delegation|
112
+ assert_equal Hashie::Array, delegation.class, delegation.inspect
113
+ end
114
+ end
115
+ end
116
+
117
+ def test_get_witness_by_account
118
+ vcr_cassette('get_witness_by_account') do
119
+ @api.get_witness_by_account('') do |witness|
120
+ assert_equal NilClass, witness.class, witness.inspect
121
+ end
122
+ end
123
+ end
124
+
125
+ def test_recover_transaction
126
+ vcr_cassette('recover_transaction') do
127
+ assert_nil @api.send(:recover_transaction, [], 1, Time.now.utc), 'expect nil response from recover_transaction'
128
+ end
129
+ end
130
+
131
+ def test_backoff
132
+ assert_equal 0, @api.send(:backoff)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+
3
+ module Radiator
4
+ class ChainStatsApiTest < Radiator::Test
5
+ def setup
6
+ vcr_cassette('chain_stats_api_jsonrpc') do
7
+ @api = Radiator::ChainStatsApi.new(chain_options)
8
+ end
9
+ end
10
+
11
+ def test_method_missing
12
+ assert_raises NoMethodError do
13
+ @api.bogus
14
+ end
15
+ end
16
+
17
+ def test_all_respond_to
18
+ vcr_cassette('chain_stats_api_all_respond_to') do
19
+ @api.method_names.each do |key|
20
+ assert @api.respond_to?(key), "expect rpc respond to #{key}"
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_all_methods
26
+ vcr_cassette('chain_stats_api_all_methods') do
27
+ skip 'This plugin is not typically enabled.'
28
+
29
+ @api.method_names.each do |key|
30
+ begin
31
+ assert @api.send key
32
+ rescue Steem::ArgumentError => e
33
+ # next
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def test_get_stats_for_time
40
+ skip 'This plugin is not typically enabled.'
41
+
42
+ vcr_cassette('get_stats_for_time') do
43
+ @api.get_stats_for_time("20161031T235959", 1000) do |stats|
44
+ assert_equal NilClass, stats.class, stats.inspect
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,153 @@
1
+ require 'test_helper'
2
+
3
+ module Radiator
4
+ class ChainTest < Radiator::Test
5
+ def setup
6
+ options = {
7
+ chain: :steem,
8
+ account_name: 'social',
9
+ wif: '5JrvPrQeBBvCRdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC'
10
+ }
11
+
12
+ @chain = Radiator::Chain.new(options)
13
+ end
14
+
15
+ def test_parse_slug
16
+ author, permlink = Radiator::Chain.parse_slug '@author/permlink'
17
+
18
+ assert_equal 'author', author
19
+ assert_equal 'permlink', permlink
20
+ end
21
+
22
+ def test_parse_slug_no_at
23
+ author, permlink = Radiator::Chain.parse_slug 'author/permlink'
24
+
25
+ assert_equal 'author', author
26
+ assert_equal 'permlink', permlink
27
+ end
28
+
29
+ def test_parse_slug_to_comment_with_comments_anchor
30
+ url = 'https://steemit.com/chainbb-general/@howtostartablog/the-joke-is-always-in-the-comments-8-sbd-contest#comments'
31
+ author, permlink = Radiator::Chain.parse_slug url
32
+
33
+ assert_equal 'howtostartablog', author
34
+ assert_equal 'the-joke-is-always-in-the-comments-8-sbd-contest', permlink
35
+ end
36
+
37
+ def test_parse_slug_to_comment_with_apache_slash
38
+ url = 'https://steemit.com/chainbb-general/@howtostartablog/the-joke-is-always-in-the-comments-8-sbd-contest/'
39
+ author, permlink = Radiator::Chain.parse_slug url
40
+
41
+ assert_equal 'howtostartablog', author
42
+ assert_equal 'the-joke-is-always-in-the-comments-8-sbd-contest', permlink
43
+ end
44
+
45
+ def test_parse_slug_to_comment
46
+ url = 'https://steemit.com/chainbb-general/@howtostartablog/the-joke-is-always-in-the-comments-8-sbd-contest#@btcvenom/re-howtostartablog-the-joke-is-always-in-the-comments-8-sbd-contest-20170624t115213474z'
47
+ author, permlink = Radiator::Chain.parse_slug url
48
+
49
+ assert_equal 'btcvenom', author
50
+ assert_equal 're-howtostartablog-the-joke-is-always-in-the-comments-8-sbd-contest-20170624t115213474z', permlink
51
+ end
52
+
53
+ def test_parse_slug_to_comment_no_at
54
+ url = 'btcvenom/re-howtostartablog-the-joke-is-always-in-the-comments-8-sbd-contest-20170624t115213474z'
55
+ author, permlink = Radiator::Chain.parse_slug url
56
+
57
+ assert_equal 'btcvenom', author
58
+ assert_equal 're-howtostartablog-the-joke-is-always-in-the-comments-8-sbd-contest-20170624t115213474z', permlink
59
+ end
60
+
61
+ def test_find_block
62
+ vcr_cassette('find_block') do
63
+ refute_nil @chain.find_block(424377)
64
+ end
65
+ end
66
+
67
+ def test_find_account
68
+ vcr_cassette('find_account') do
69
+ refute_nil @chain.find_account('ned')
70
+ end
71
+ end
72
+
73
+ def test_find_comment
74
+ vcr_cassette('find_comment') do
75
+ refute_nil @chain.find_comment('inertia', 'kinda-spooky')
76
+ end
77
+ end
78
+
79
+ def test_find_comment_with_slug
80
+ vcr_cassette('find_comment') do
81
+ refute_nil @chain.find_comment('@inertia/kinda-spooky')
82
+ end
83
+ end
84
+
85
+ def test_find_comment_with_slug_and_comments_anchor
86
+ vcr_cassette('find_comment') do
87
+ refute_nil @chain.find_comment('@inertia/kinda-spooky#comments')
88
+ end
89
+ end
90
+
91
+ def test_properties
92
+ vcr_cassette('properties') do
93
+ refute_nil @chain.properties
94
+ end
95
+ end
96
+
97
+ def test_block_time
98
+ vcr_cassette('block_time') do
99
+ refute_nil @chain.block_time
100
+ end
101
+ end
102
+
103
+ def test_base_per_mvest
104
+ vcr_cassette('base_per_mvest') do
105
+ refute_nil @chain.base_per_mvest
106
+ end
107
+ end
108
+
109
+ def test_base_per_debt
110
+ vcr_cassette('base_per_debt') do
111
+ refute_nil @chain.base_per_debt
112
+ end
113
+ end
114
+
115
+ def test_followed_by
116
+ skip
117
+ vcr_cassette('followed_by') do
118
+ refute_nil @chain.followed_by('inertia')
119
+ end
120
+ end
121
+
122
+ def test_following
123
+ skip
124
+ vcr_cassette('following') do
125
+ refute_nil @chain.following('inertia')
126
+ end
127
+ end
128
+
129
+ def test_post!
130
+ skip 'Seems like archived post edits are now possible, so we will skip this test to avoid spamming.'
131
+
132
+ options = {
133
+ title: 'title of my post',
134
+ body: 'body of my post (archive: edited)',
135
+ tags: ['tag'],
136
+ self_upvote: 10000,
137
+ percent_steem_dollars: 0
138
+ }
139
+
140
+ vcr_cassette('post!') do
141
+ result = @chain.post!(options)
142
+ refute_nil result
143
+ assert_equal ErrorParser, result.class, "expect ErrorParser, got result: #{result}"
144
+
145
+ # Note: pre-appbase, this was the error:
146
+ # assert_equal '4100000: The comment is archived', result.to_s
147
+
148
+ # Now, this is what we get:
149
+ assert_equal '10: _callbacks.find( txid ) == _callbacks.end(): Transaction is a duplicate', result.to_s
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+
3
+ module Radiator
4
+ class CondenserApiTest < Radiator::Test
5
+ def setup
6
+ vcr_cassette('condenser_api_jsonrpc') do
7
+ @api = Radiator::CondenserApi.new(chain_options)
8
+ @silent_api = Radiator::CondenserApi.new(chain_options.merge(logger: LOGGER))
9
+ end
10
+ end
11
+
12
+ def test_method_missing
13
+ assert_raises NoMethodError do
14
+ @api.bogus
15
+ end
16
+ end
17
+
18
+ def test_all_respond_to
19
+ vcr_cassette('condenser_api_all_respond_to') do
20
+ @api.method_names.each do |key|
21
+ assert @api.respond_to?(key), "expect rpc respond to #{key}"
22
+ end
23
+ end
24
+ end
25
+
26
+ def test_all_methods
27
+ vcr_cassette('condenser_all_all_methods') do
28
+ @silent_api.method_names.each do |key|
29
+ begin
30
+ assert @silent_api.send key
31
+ rescue Steem::ArgumentError => e
32
+ # next
33
+ rescue Steem::RemoteNodeError => e
34
+ # next
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def test_look_up_witnesses
41
+ vcr_cassette('look_up_witnesses') do
42
+ @api.lookup_witness_accounts('', 19) do |witnesses|
43
+ assert_equal Hashie::Array, witnesses.class, witnesses.inspect
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+
3
+ module Radiator
4
+ class FollowApiTest < Radiator::Test
5
+ def setup
6
+ vcr_cassette('follow_api_jsonrpc') do
7
+ @api = Radiator::FollowApi.new(chain_options)
8
+ end
9
+ end
10
+
11
+ def test_method_missing
12
+ assert_raises NoMethodError do
13
+ @api.bogus
14
+ end
15
+ end
16
+
17
+ def test_all_respond_to
18
+ vcr_cassette('follow_api_all_respond_to') do
19
+ @api.method_names.each do |key|
20
+ assert @api.respond_to?(key), "expect rpc respond to #{key}"
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_all_methods
26
+ vcr_cassette('follow_api_all_methods') do
27
+ skip
28
+ @api.method_names.each do |key|
29
+ begin
30
+ assert @api.send key
31
+ rescue Steem::ArgumentError => e
32
+ # next
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def test_get_followers
39
+ skip
40
+ vcr_cassette('get_followers') do
41
+ @api.get_followers(account: 'inertia', start: 0, type: 'blog', limit: 100) do |followers|
42
+ assert_equal Hashie::Array, followers.class, followers.inspect
43
+ assert followers
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end