rhcp 0.1.9 → 0.2.14
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.
- data/lib/rhcp.rb +6 -2
- data/lib/rhcp/broker.rb +68 -11
- data/lib/rhcp/client/command_param_stub.rb +4 -3
- data/lib/rhcp/client/context_aware_broker.rb +78 -0
- data/lib/rhcp/client/http_broker.rb +29 -9
- data/lib/rhcp/command.rb +122 -17
- data/lib/rhcp/command_param.rb +34 -6
- data/lib/rhcp/context.rb +43 -0
- data/lib/rhcp/dispatching_broker.rb +35 -11
- data/lib/rhcp/http_exporter.rb +51 -18
- data/lib/rhcp/logging_broker.rb +85 -0
- data/lib/rhcp/memcached_broker.rb +120 -0
- data/lib/rhcp/request.rb +43 -24
- data/lib/rhcp/response.rb +16 -3
- data/test/rhcp/broker_test.rb +10 -20
- data/test/rhcp/client/command_param_stub_test.rb +15 -5
- data/test/rhcp/client/command_stub_test.rb +1 -1
- data/test/rhcp/command_param_test.rb +22 -37
- data/test/rhcp/command_test.rb +60 -2
- data/test/rhcp/context_aware_broker_test.rb +86 -0
- data/test/rhcp/context_test.rb +16 -0
- data/test/rhcp/dispatching_broker_test.rb +20 -26
- data/test/rhcp/http_broker_test.rb +33 -0
- data/test/rhcp/http_exporter_test.rb +45 -8
- data/test/rhcp/http_test_server.rb +41 -2
- data/test/rhcp/logging_broker_test.rb +22 -0
- data/test/rhcp/request_test.rb +63 -27
- data/test/rhcp/response_test.rb +16 -6
- data/test/rhcp/test_base.rb +25 -0
- data/test/rhcp/tests_for_brokers.rb +186 -0
- data/test/rhcp/tests_for_writable_brokers.rb +22 -0
- metadata +54 -28
- data/test/rhcp/http_registry_test.rb +0 -73
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..','lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'rhcp'
|
6
|
+
require 'rhcp/context'
|
7
|
+
|
8
|
+
class ContextTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def test_the_context
|
11
|
+
context = RHCP::Context.new({'nice' => 'sun'})
|
12
|
+
p context
|
13
|
+
puts context.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -1,40 +1,34 @@
|
|
1
1
|
|
2
|
-
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..','lib')
|
3
3
|
|
4
4
|
require 'test/unit'
|
5
|
-
#require 'rhcp/dispatching_broker'
|
6
|
-
#require 'rhcp/broker'
|
7
|
-
#require 'rhcp/command'
|
8
|
-
#require 'rhcp/command_param'
|
9
5
|
require 'rhcp'
|
6
|
+
require 'rhcp/tests_for_brokers'
|
10
7
|
|
11
8
|
class DispatchingBrokerTest < Test::Unit::TestCase
|
12
|
-
|
9
|
+
|
10
|
+
include TestsForBrokers
|
11
|
+
|
13
12
|
def setup
|
14
|
-
@
|
15
|
-
|
16
|
-
@broker1.register_command RHCP::Command.new("test_more", "another fancy command from broker1", lambda{})
|
13
|
+
@broker = RHCP::Broker.new()
|
14
|
+
|
17
15
|
@broker2 = RHCP::Broker.new()
|
16
|
+
|
17
|
+
@test_broker = RHCP::DispatchingBroker.new()
|
18
|
+
@test_broker.add_broker(@broker)
|
19
|
+
@test_broker.add_broker(@broker2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_duplicate_commands
|
23
|
+
@broker.register_command RHCP::Command.new("funky_stuff", "just testing (broker1)", lambda{})
|
24
|
+
assert_equal 1, @test_broker.get_command_list().size()
|
18
25
|
@broker2.register_command RHCP::Command.new("echo", "says hello (broker2)", lambda{})
|
19
26
|
@broker2.register_command RHCP::Command.new("help", "is no help at all (broker2)", lambda{})
|
20
27
|
@broker2.register_command RHCP::Command.new("red button", "don't press it (broker2)", lambda{})
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
assert_not_nil dispatcher
|
26
|
-
assert_equal 0, dispatcher.get_command_list.size
|
27
|
-
dispatcher.add_broker(@broker1)
|
28
|
-
assert_equal 2, dispatcher.get_command_list.size
|
29
|
-
dispatcher.add_broker(@broker2)
|
30
|
-
assert_equal 5, dispatcher.get_command_list.size
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_duplicate_commands
|
34
|
-
dispatcher = RHCP::DispatchingBroker.new()
|
35
|
-
dispatcher.add_broker(@broker1)
|
36
|
-
assert_equal 2, dispatcher.get_command_list.size
|
37
|
-
assert_raise(RHCP::RhcpException) { dispatcher.add_broker(@broker1) }
|
28
|
+
assert_equal 4, @test_broker.get_command_list().size()
|
29
|
+
|
30
|
+
assert_raise(RHCP::RhcpException) { @test_broker.add_broker(@broker2) }
|
31
|
+
#assert_equal 4, @test_broker.get_command_list().size()
|
38
32
|
end
|
39
33
|
|
40
34
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..','lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rhcp'
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'rhcp/tests_for_brokers'
|
8
|
+
|
9
|
+
class HttpBrokerTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
include TestsForBrokers
|
12
|
+
|
13
|
+
def self.suite
|
14
|
+
@@broker = RHCP::Broker.new()
|
15
|
+
|
16
|
+
@@exporter = RHCP::HttpExporter.new(@@broker, :port => 42001)
|
17
|
+
@@exporter.start()
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup
|
22
|
+
RHCP::ModuleHelper.instance.logger.level = Logger::DEBUG
|
23
|
+
$logger.level = Logger::DEBUG
|
24
|
+
@broker = @@broker
|
25
|
+
@broker.clear
|
26
|
+
url = URI.parse("http://localhost:42001")
|
27
|
+
@test_broker = RHCP::Client::HttpBroker.new(url)
|
28
|
+
#@test_broker = RHCP::Client::ContextAwareBroker.new(broker)
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO test with a real second VM
|
32
|
+
|
33
|
+
end
|
@@ -1,12 +1,13 @@
|
|
1
|
-
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..','lib')
|
2
2
|
|
3
|
+
require 'test_base'
|
3
4
|
require 'test/unit'
|
4
5
|
require 'net/http'
|
5
6
|
|
6
7
|
require 'rhcp'
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
|
10
|
+
class HttpExporterTest < TestBase
|
10
11
|
|
11
12
|
def self.suite
|
12
13
|
puts "gonna setup"
|
@@ -20,6 +21,10 @@ class HttpExporterTest < Test::Unit::TestCase
|
|
20
21
|
}))
|
21
22
|
)
|
22
23
|
|
24
|
+
command = RHCP::Command.new("host_command", "a test command that is enabled for hosts only", lambda {})
|
25
|
+
command.enabled_through_context_keys = ['host']
|
26
|
+
broker.register_command command
|
27
|
+
|
23
28
|
@@broker = broker
|
24
29
|
|
25
30
|
# TODO test other setup options
|
@@ -55,7 +60,7 @@ class HttpExporterTest < Test::Unit::TestCase
|
|
55
60
|
assert_equal "200", res.code
|
56
61
|
commands = JSON.parse(res.body)
|
57
62
|
|
58
|
-
# +commands+ is an array => convert it back to a
|
63
|
+
# +commands+ is an array => convert it back to a hash
|
59
64
|
command_hash = Hash.new()
|
60
65
|
commands.each do |command_string|
|
61
66
|
command = RHCP::Client::CommandStub.reconstruct_from_json(command_string)
|
@@ -66,8 +71,33 @@ class HttpExporterTest < Test::Unit::TestCase
|
|
66
71
|
assert_kind_of RHCP::Command, command_hash["reverse"]
|
67
72
|
end
|
68
73
|
|
74
|
+
# we should be able to activate another command by setting the 'host' context cookie
|
75
|
+
def test_get_commands_with_context
|
76
|
+
context = RHCP::Context.new()
|
77
|
+
context.cookies['host'] = 'deepthought'
|
78
|
+
|
79
|
+
res = Net::HTTP.new(@url.host, @url.port).start { |http| http.post("/rhcp/get_commands", context.to_json) }
|
80
|
+
|
81
|
+
assert_equal "200", res.code
|
82
|
+
commands = JSON.parse(res.body)
|
83
|
+
|
84
|
+
# +commands+ is an array => convert it back to a hash
|
85
|
+
command_hash = Hash.new()
|
86
|
+
commands.each do |command_string|
|
87
|
+
command = RHCP::Client::CommandStub.reconstruct_from_json(command_string)
|
88
|
+
command_hash[command.name] = command
|
89
|
+
end
|
90
|
+
|
91
|
+
assert_equal [ "test", "reverse", "host_command" ].sort, command_hash.keys.sort
|
92
|
+
end
|
93
|
+
|
69
94
|
def test_get_lookup_values
|
70
|
-
|
95
|
+
context = RHCP::Context.new()
|
96
|
+
context.cookies['host'] = 'deepthought'
|
97
|
+
command = RHCP::Command.new('reverse', '', lambda { |req,res|})
|
98
|
+
request = RHCP::Request.new(command, {}, context)
|
99
|
+
|
100
|
+
res = Net::HTTP.new(@url.host, @url.port).start { |http| http.post("/rhcp/get_lookup_values?command=reverse¶m=input", request.to_json) }
|
71
101
|
puts res.body
|
72
102
|
assert_equal "200", res.code
|
73
103
|
lookups = JSON.parse(res.body)
|
@@ -75,11 +105,16 @@ class HttpExporterTest < Test::Unit::TestCase
|
|
75
105
|
end
|
76
106
|
|
77
107
|
def test_get_lookup_values_invalid_command
|
78
|
-
|
108
|
+
context = RHCP::Context.new()
|
109
|
+
context.cookies['host'] = 'deepthought'
|
110
|
+
command = RHCP::Command.new('do_the_twist', '', lambda { |req,res|})
|
111
|
+
request = RHCP::Request.new(command, {}, context)
|
112
|
+
|
113
|
+
res = Net::HTTP.new(@url.host, @url.port).start { |http| http.post("/rhcp/get_lookup_values?command=do_the_twist", request.to_json ) }
|
79
114
|
puts "error response #{res.body}"
|
80
115
|
assert_equal "500", res.code
|
81
|
-
|
82
|
-
assert(/^no such command/.match(
|
116
|
+
response = RHCP::Response.reconstruct_from_json(res.body)
|
117
|
+
assert(/^no such command/.match(response.error_text))
|
83
118
|
end
|
84
119
|
|
85
120
|
def test_get_lookup_values_without_params
|
@@ -88,7 +123,9 @@ class HttpExporterTest < Test::Unit::TestCase
|
|
88
123
|
assert_equal "500", res.code
|
89
124
|
end
|
90
125
|
|
126
|
+
# TODO reactivate
|
91
127
|
def test_get_lookup_values_partial
|
128
|
+
return
|
92
129
|
res = Net::HTTP.new(@url.host, @url.port).start { |http| http.get("/rhcp/get_lookup_values?command=reverse¶m=input&partial=zap") }
|
93
130
|
puts res.body
|
94
131
|
assert_equal "200", res.code
|
@@ -8,7 +8,13 @@ require 'logger'
|
|
8
8
|
$logger = Logger.new($stdout)
|
9
9
|
|
10
10
|
broker = RHCP::Broker.new()
|
11
|
-
|
11
|
+
test_command = RHCP::Command.new("test", "just a test command", lambda { |req,res| "testing" })
|
12
|
+
broker.register_command(test_command)
|
13
|
+
|
14
|
+
hello_command = RHCP::Command.new("hello", "another test command", lambda { |req, res| "hello world" })
|
15
|
+
hello_command.mark_as_read_only()
|
16
|
+
broker.register_command(hello_command)
|
17
|
+
|
12
18
|
broker.register_command(
|
13
19
|
RHCP::Command.new("reverse", "reversing input strings",
|
14
20
|
lambda { |req,res| req.get_param_value("input").reverse }
|
@@ -30,8 +36,23 @@ broker.register_command RHCP::Command.new("cook", "cook something nice out of so
|
|
30
36
|
}
|
31
37
|
)
|
32
38
|
)
|
39
|
+
|
40
|
+
def colorize(text, color_code)
|
41
|
+
"\e[#{color_code}m#{text}\e[0m"
|
42
|
+
end
|
43
|
+
|
44
|
+
def red(text)
|
45
|
+
colorize(text, 31)
|
46
|
+
end
|
47
|
+
|
48
|
+
def green(text)
|
49
|
+
colorize(text, 32)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
33
53
|
command = RHCP::Command.new("list_stuff", "this command lists stuff",
|
34
54
|
lambda { |req,res|
|
55
|
+
res.set_context({"prompt" => green("what have the romans brought us $ ")})
|
35
56
|
[ "peace", "aquaeduct", "education" ]
|
36
57
|
}
|
37
58
|
)
|
@@ -54,7 +75,25 @@ command.result_hints[:overview_columns] = [ "the_first_name", "last_name" ]
|
|
54
75
|
command.result_hints[:column_titles] = [ "First Name", "Last Name" ]
|
55
76
|
broker.register_command command
|
56
77
|
|
57
|
-
|
78
|
+
logging_broker = RHCP::LoggingBroker.new(broker)
|
79
|
+
context_aware_broker = RHCP::Client::ContextAwareBroker.new(logging_broker)
|
80
|
+
|
81
|
+
complicated_command = RHCP::Command.new("complicated", "just testing",
|
82
|
+
lambda { |req, res|
|
83
|
+
request = RHCP::Request.new(test_command, Hash.new())
|
84
|
+
context_aware_broker.execute(request)
|
85
|
+
|
86
|
+
hello_request = RHCP::Request.new(hello_command, Hash.new())
|
87
|
+
context_aware_broker.execute(hello_request)
|
88
|
+
|
89
|
+
hello_request2 = RHCP::Request.new(hello_command, Hash.new())
|
90
|
+
context_aware_broker.execute(hello_request2)
|
91
|
+
}
|
92
|
+
)
|
93
|
+
broker.register_command complicated_command
|
94
|
+
|
95
|
+
|
96
|
+
exporter = RHCP::HttpExporter.new(logging_broker, :port => 42000)
|
58
97
|
|
59
98
|
trap("INT") {
|
60
99
|
exporter.stop
|
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..','lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rhcp'
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'rhcp/tests_for_brokers'
|
8
|
+
require 'rhcp/tests_for_writable_brokers'
|
9
|
+
|
10
|
+
class LoggingBrokerTest < Test::Unit::TestCase
|
11
|
+
|
12
|
+
include TestsForBrokers
|
13
|
+
include TestsForWritableBrokers
|
14
|
+
|
15
|
+
def setup
|
16
|
+
broker = RHCP::Broker.new()
|
17
|
+
|
18
|
+
@broker = broker
|
19
|
+
@test_broker = RHCP::LoggingBroker.new(broker)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/test/rhcp/request_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..','lib')
|
2
2
|
|
3
3
|
require 'test/unit'
|
4
4
|
require 'rhcp'
|
@@ -46,33 +46,11 @@ class RequestTest < Test::Unit::TestCase
|
|
46
46
|
assert ! request.has_param_value("third_param")
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
50
|
-
command = RHCP::Command.new("test", "another test", lambda {})
|
51
|
-
command.add_param(RHCP::CommandParam.new("real_param", "this param does exist"))
|
52
|
-
assert_not_nil RHCP::Request.new(command, { "real_param" => [ "value for the real param" ] })
|
53
|
-
assert_raise(RHCP::RhcpException) { RHCP::Request.new(command, {
|
54
|
-
"does_not_exist" => [ "single value for non-existing param" ]
|
55
|
-
}) }
|
56
|
-
end
|
57
|
-
|
58
|
-
def test_invalid_param_values
|
59
|
-
command = RHCP::Command.new("test", "another test", lambda {})
|
60
|
-
command.add_param(RHCP::CommandParam.new("real_param", "this param does exist"))
|
61
|
-
assert_raise(RHCP::RhcpException) { RHCP::Request.new(command, {
|
62
|
-
"real_param" => [ "value for the real param", "another value for the real param" ]
|
63
|
-
}) }
|
64
|
-
end
|
65
|
-
|
66
|
-
def test_missing_mandatory_param
|
49
|
+
def test_partial_request
|
67
50
|
command = RHCP::Command.new("test", "another test", lambda {})
|
68
51
|
command.add_param(RHCP::CommandParam.new("first_param", "this is the first param", { :mandatory => true }))
|
69
|
-
|
70
|
-
|
71
|
-
# test1 : we should not have to specify optional parameters
|
72
|
-
assert_not_nil RHCP::Request.new(command, { "first_param" => [ "bla" ]})
|
73
|
-
|
74
|
-
# test2 : but we need to specify mandatory params
|
75
|
-
assert_raise(RHCP::RhcpException) { RHCP::Request.new(command, {})}
|
52
|
+
partial_request = RHCP::Request.new(command, {})
|
53
|
+
assert_not_nil partial_request
|
76
54
|
end
|
77
55
|
|
78
56
|
def test_json
|
@@ -86,7 +64,10 @@ class RequestTest < Test::Unit::TestCase
|
|
86
64
|
{
|
87
65
|
"first_param" => ["foo"],
|
88
66
|
"second_param" => ["foo", "bar", "baz"]
|
89
|
-
}
|
67
|
+
},
|
68
|
+
RHCP::Context.new({
|
69
|
+
"juliet" => "naked"
|
70
|
+
})
|
90
71
|
)
|
91
72
|
json = r.to_json
|
92
73
|
puts "request as JSON : >>#{json}<<"
|
@@ -95,6 +76,7 @@ class RequestTest < Test::Unit::TestCase
|
|
95
76
|
assert_instance_of RHCP::Request, r2
|
96
77
|
assert_equal r.command, r2.command
|
97
78
|
assert_equal r.param_values, r2.param_values
|
79
|
+
assert_equal r.context.cookies, r2.context.cookies
|
98
80
|
end
|
99
81
|
|
100
82
|
def test_execute
|
@@ -120,5 +102,59 @@ class RequestTest < Test::Unit::TestCase
|
|
120
102
|
assert_not_nil res
|
121
103
|
assert_equal "***lucky bastard***", res.data
|
122
104
|
end
|
105
|
+
|
106
|
+
def test_request_with_context
|
107
|
+
command = RHCP::Command.new("gimme_context", "you provide the food, i provide the context", lambda { |req,res| req.get_param_value("the_host") })
|
108
|
+
command.add_param(RHCP::CommandParam.new("the_host", "the value that will be filled throught the context", {
|
109
|
+
:mandatory => true,
|
110
|
+
:autofill_context_key => 'host'
|
111
|
+
}))
|
112
|
+
command.add_param(RHCP::CommandParam.new("second_param", "second param", {
|
113
|
+
:mandatory => true
|
114
|
+
}))
|
115
|
+
|
116
|
+
r= RHCP::Request.new(
|
117
|
+
command,
|
118
|
+
{
|
119
|
+
"the_host" => "deepthought",
|
120
|
+
"second_param" => "bla"
|
121
|
+
}
|
122
|
+
)
|
123
|
+
res = r.execute()
|
124
|
+
assert_not_nil res
|
125
|
+
assert_equal "deepthought", res.data
|
126
|
+
|
127
|
+
|
128
|
+
r= RHCP::Request.new(
|
129
|
+
command,
|
130
|
+
{"second_param" => "bla"},
|
131
|
+
RHCP::Context.new({'host' => "endeavour"})
|
132
|
+
)
|
133
|
+
res = r.execute()
|
134
|
+
assert_not_nil res
|
135
|
+
assert_equal "endeavour", res.data
|
136
|
+
end
|
137
|
+
|
138
|
+
# if a parameter is specified both in the context and in the parameter values,
|
139
|
+
# the parameter values shall rule
|
140
|
+
def test_param_values_context_precedence
|
141
|
+
command = RHCP::Command.new("gimme_context", "you provide the food, i provide the context", lambda { |req,res| req.get_param_value("the_host") })
|
142
|
+
command.add_param(RHCP::CommandParam.new("the_host", "the value that will be filled throught the context", {
|
143
|
+
:mandatory => true,
|
144
|
+
:autofill_context_key => 'host'
|
145
|
+
}))
|
146
|
+
command.add_param(RHCP::CommandParam.new("second_param", "second param", {
|
147
|
+
:mandatory => true
|
148
|
+
}))
|
149
|
+
|
150
|
+
r= RHCP::Request.new(
|
151
|
+
command,
|
152
|
+
{"second_param" => "bla", "the_host" => "deepthought"},
|
153
|
+
RHCP::Context.new({'host' => "endeavour"})
|
154
|
+
)
|
155
|
+
res = r.execute()
|
156
|
+
assert_not_nil res
|
157
|
+
assert_equal "deepthought", res.data
|
158
|
+
end
|
123
159
|
|
124
160
|
end
|
data/test/rhcp/response_test.rb
CHANGED
@@ -1,9 +1,4 @@
|
|
1
|
-
|
2
|
-
# To change this template, choose Tools | Templates
|
3
|
-
# and open the template in the editor.
|
4
|
-
|
5
|
-
|
6
|
-
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','..','lib')
|
7
2
|
|
8
3
|
require 'test/unit'
|
9
4
|
require 'rhcp/response'
|
@@ -41,5 +36,20 @@ class ResponseTest < Test::Unit::TestCase
|
|
41
36
|
assert_equal "things got really fucked up", r2.error_detail
|
42
37
|
assert_equal "some data", r2.data
|
43
38
|
end
|
39
|
+
|
40
|
+
def test_with_context
|
41
|
+
response = RHCP::Response.new()
|
42
|
+
response.set_payload("some data")
|
43
|
+
response.set_context({'cookies' => 'good'})
|
44
|
+
assert_equal 'good', response.context['cookies']
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_json_with_context
|
48
|
+
response = RHCP::Response.new()
|
49
|
+
response.set_payload("some data")
|
50
|
+
response.set_context({'cookies' => 'good'})
|
51
|
+
r2 = RHCP::Response.reconstruct_from_json(response.to_json())
|
52
|
+
assert_equal response.context, r2.context
|
53
|
+
end
|
44
54
|
|
45
55
|
end
|