webmock 0.8.2 → 0.9.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.
- data/CHANGELOG.md +113 -0
- data/README.md +84 -18
- data/VERSION +1 -1
- data/lib/webmock.rb +1 -0
- data/lib/webmock/adapters/rspec/webmock_matcher.rb +2 -2
- data/lib/webmock/http_lib_adapters/net_http.rb +11 -13
- data/lib/webmock/request.rb +1 -1
- data/lib/webmock/request_profile.rb +11 -2
- data/lib/webmock/request_signature.rb +8 -2
- data/lib/webmock/request_stub.rb +39 -11
- data/lib/webmock/response.rb +32 -1
- data/lib/webmock/responses_sequence.rb +40 -0
- data/lib/webmock/util/headers.rb +1 -1
- data/lib/webmock/webmock.rb +4 -4
- data/spec/example_curl_output.txt +20 -0
- data/spec/httpclient_spec_helper.rb +4 -0
- data/spec/net_http_spec.rb +8 -0
- data/spec/net_http_spec_helper.rb +10 -1
- data/spec/request_profile_spec.rb +5 -0
- data/spec/request_registry_spec.rb +4 -4
- data/spec/request_signature_spec.rb +35 -0
- data/spec/request_stub_spec.rb +123 -0
- data/spec/response_spec.rb +72 -7
- data/spec/spec_helper.rb +27 -0
- data/spec/webmock_spec.rb +674 -330
- data/test/test_webmock.rb +2 -1
- data/webmock.gemspec +5 -3
- metadata +5 -3
- data/CHANGELOG +0 -36
data/lib/webmock/response.rb
CHANGED
@@ -1,9 +1,19 @@
|
|
1
|
+
#compatibility with Ruby 1.9.2 preview1 to allow reading raw responses
|
2
|
+
class StringIO
|
3
|
+
alias_method :read_nonblock, :sysread
|
4
|
+
end
|
5
|
+
|
1
6
|
module WebMock
|
7
|
+
|
2
8
|
class Response
|
3
9
|
attr_reader :options
|
4
10
|
|
5
11
|
def initialize(options = {})
|
6
|
-
|
12
|
+
if options.is_a?(IO) || options.is_a?(String)
|
13
|
+
self.options = read_raw_response(options)
|
14
|
+
else
|
15
|
+
self.options = options
|
16
|
+
end
|
7
17
|
@options[:headers] = Util::Headers.normalize_headers(@options[:headers]) unless @options[:headers].is_a?(Proc)
|
8
18
|
end
|
9
19
|
|
@@ -39,6 +49,8 @@ module WebMock
|
|
39
49
|
def ==(other)
|
40
50
|
options == other.options
|
41
51
|
end
|
52
|
+
|
53
|
+
private
|
42
54
|
|
43
55
|
def stringify_body!
|
44
56
|
if @options[:body].is_a?(IO)
|
@@ -47,6 +59,25 @@ module WebMock
|
|
47
59
|
io.close
|
48
60
|
end
|
49
61
|
end
|
62
|
+
|
63
|
+
def read_raw_response(raw_response)
|
64
|
+
if raw_response.is_a?(IO)
|
65
|
+
string = raw_response.read
|
66
|
+
raw_response.close
|
67
|
+
raw_response = string
|
68
|
+
end
|
69
|
+
socket = Net::BufferedIO.new(raw_response)
|
70
|
+
response = Net::HTTPResponse.read_new(socket)
|
71
|
+
transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
|
72
|
+
response.reading_body(socket, true) {}
|
73
|
+
|
74
|
+
options = {}
|
75
|
+
options[:headers] = {}
|
76
|
+
response.each_header {|name, value| options[:headers][name] = value}
|
77
|
+
options[:headers]['transfer-encoding'] = transfer_encoding if transfer_encoding
|
78
|
+
options[:body] = response.read_body
|
79
|
+
options
|
80
|
+
end
|
50
81
|
|
51
82
|
end
|
52
83
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module WebMock
|
2
|
+
|
3
|
+
class ResponsesSequence
|
4
|
+
|
5
|
+
attr_accessor :times_to_repeat
|
6
|
+
|
7
|
+
def initialize(responses)
|
8
|
+
@times_to_repeat = 1
|
9
|
+
@responses = responses
|
10
|
+
@current_position = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def end?
|
14
|
+
@times_to_repeat == 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def next_response
|
18
|
+
if @times_to_repeat > 0
|
19
|
+
response = @responses[@current_position]
|
20
|
+
increase_position
|
21
|
+
response
|
22
|
+
else
|
23
|
+
@responses.last
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def increase_position
|
30
|
+
if @current_position == (@responses.length - 1)
|
31
|
+
@current_position = 0
|
32
|
+
@times_to_repeat -= 1
|
33
|
+
else
|
34
|
+
@current_position += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/lib/webmock/util/headers.rb
CHANGED
@@ -7,7 +7,7 @@ module WebMock
|
|
7
7
|
def self.normalize_headers(headers)
|
8
8
|
return nil unless headers
|
9
9
|
array = headers.map { |name, value|
|
10
|
-
[name.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-"), value.to_s]
|
10
|
+
[name.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-"), value.is_a?(Regexp) ? value : value.to_s]
|
11
11
|
}
|
12
12
|
Hash[*array.flatten]
|
13
13
|
end
|
data/lib/webmock/webmock.rb
CHANGED
@@ -11,15 +11,15 @@ module WebMock
|
|
11
11
|
RequestProfile.new(method, uri)
|
12
12
|
end
|
13
13
|
|
14
|
-
def assert_requested(method, uri, options = {})
|
14
|
+
def assert_requested(method, uri, options = {}, &block)
|
15
15
|
expected_times_executed = options.delete(:times) || 1
|
16
|
-
request = RequestProfile.new(method, uri, options)
|
16
|
+
request = RequestProfile.new(method, uri, options).with(&block)
|
17
17
|
verifier = RequestExecutionVerifier.new(request, expected_times_executed)
|
18
18
|
assertion_failure(verifier.failure_message) unless verifier.matches?
|
19
19
|
end
|
20
20
|
|
21
|
-
def assert_not_requested(method, uri, options = {})
|
22
|
-
request = RequestProfile.new(method, uri, options)
|
21
|
+
def assert_not_requested(method, uri, options = {}, &block)
|
22
|
+
request = RequestProfile.new(method, uri, options).with(&block)
|
23
23
|
verifier = RequestExecutionVerifier.new(request, options.delete(:times))
|
24
24
|
assertion_failure(verifier.negative_failure_message) unless verifier.does_not_match?
|
25
25
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Content-Type: text/html; charset=UTF-8
|
3
|
+
Connection: Keep-Alive
|
4
|
+
Date: Sat, 23 Jan 2010 01:01:05 GMT
|
5
|
+
Content-Length: 438
|
6
|
+
|
7
|
+
<HTML>
|
8
|
+
<HEAD>
|
9
|
+
<TITLE>Example Web Page</TITLE>
|
10
|
+
</HEAD>
|
11
|
+
<body>
|
12
|
+
<p>You have reached this web page by typing "example.com",
|
13
|
+
"example.net",
|
14
|
+
or "example.org" into your web browser.</p>
|
15
|
+
<p>These domain names are reserved for use in documentation and are not available
|
16
|
+
for registration. See <a href="http://www.rfc-editor.org/rfc/rfc2606.txt">RFC
|
17
|
+
2606</a>, Section 3.</p>
|
18
|
+
</BODY>
|
19
|
+
</HTML>
|
20
|
+
|
@@ -23,6 +23,10 @@ module HTTPClientSpecHelper
|
|
23
23
|
:status => response.code.to_s })
|
24
24
|
end
|
25
25
|
|
26
|
+
def default_client_request_headers(request_method = nil, has_body = false)
|
27
|
+
{'Content-Type'=>'application/x-www-form-urlencoded'} if request_method == 'POST' && has_body
|
28
|
+
end
|
29
|
+
|
26
30
|
def setup_expectations_for_real_request(options = {})
|
27
31
|
socket = mock("TCPSocket")
|
28
32
|
TCPSocket.should_receive(:new).
|
data/spec/net_http_spec.rb
CHANGED
@@ -29,6 +29,13 @@ describe "Webmock with Net:HTTP" do
|
|
29
29
|
req.body = "my_params"
|
30
30
|
Net::HTTP.start("www.example.com") { |http| http.request(req)}.body.should == "abc"
|
31
31
|
end
|
32
|
+
|
33
|
+
it "should handle Net::HTTP::Post#body_stream" do
|
34
|
+
stub_http_request(:post, "www.example.com").with(:body => "my_params").to_return(:body => "abc")
|
35
|
+
req = Net::HTTP::Post.new("/")
|
36
|
+
req.body_stream = StringIO.new("my_params")
|
37
|
+
Net::HTTP.start("www.example.com") { |http| http.request(req)}.body.should == "abc"
|
38
|
+
end
|
32
39
|
|
33
40
|
it "should behave like Net::HTTP and raise error if both request body and body argument are set" do
|
34
41
|
stub_http_request(:post, "www.example.com").with(:body => "my_params").to_return(:body => "abc")
|
@@ -39,4 +46,5 @@ describe "Webmock with Net:HTTP" do
|
|
39
46
|
}.should raise_error("both of body argument and HTTPRequest#body set")
|
40
47
|
end
|
41
48
|
|
49
|
+
|
42
50
|
end
|
@@ -16,11 +16,20 @@ module NetHTTPSpecHelper
|
|
16
16
|
response = http.start {|http|
|
17
17
|
http.request(req, options[:body], &block)
|
18
18
|
}
|
19
|
+
headers = {}
|
20
|
+
response.each_header {|name, value| headers[name] = value}
|
19
21
|
OpenStruct.new({
|
20
22
|
:body => response.body,
|
21
|
-
:headers =>
|
23
|
+
:headers => WebMock::Util::Headers.normalize_headers(headers),
|
22
24
|
:status => response.code })
|
23
25
|
end
|
26
|
+
|
27
|
+
def default_client_request_headers(request_method = nil, has_body = false)
|
28
|
+
default_request = Net::HTTPGenericRequest.new('','','','/')
|
29
|
+
default_net_http_headers = Hash[*default_request.to_hash.map {|k,v|
|
30
|
+
[k, v.flatten]
|
31
|
+
}.flatten]
|
32
|
+
end
|
24
33
|
|
25
34
|
# Sets several expectations that a real HTTP request makes it
|
26
35
|
# past WebMock to the socket layer. You can use this when you need to check
|
@@ -35,6 +35,11 @@ describe RequestProfile do
|
|
35
35
|
"GET http://www.example.com/ with body 'abc' with headers {'A'=>'a', 'B'=>'b'}"
|
36
36
|
end
|
37
37
|
|
38
|
+
it "should report string describing itself with block" do
|
39
|
+
RequestProfile.new(:get, "www.example.com",
|
40
|
+
:body => "abc", :headers => {'A' => 'a', 'B' => 'b'}).with {|req| true}.to_s.should ==
|
41
|
+
"GET http://www.example.com/ with body 'abc' with headers {'A'=>'a', 'B'=>'b'} with given block"
|
42
|
+
end
|
38
43
|
|
39
44
|
describe "with" do
|
40
45
|
before(:each) do
|
@@ -50,7 +50,7 @@ describe RequestRegistry do
|
|
50
50
|
describe "response for request" do
|
51
51
|
|
52
52
|
it "should registered response for request profile" do
|
53
|
-
@request_stub.
|
53
|
+
@request_stub.instance_variable_set(:@responses, [@response = Response.new])
|
54
54
|
RequestRegistry.instance.register_request_stub(@request_stub)
|
55
55
|
RequestRegistry.instance.response_for_request(@request_signature).should == @response
|
56
56
|
end
|
@@ -61,11 +61,11 @@ describe RequestRegistry do
|
|
61
61
|
|
62
62
|
it "should always return last registered matching response" do
|
63
63
|
@request_stub1 = RequestStub.new(:get, "www.example.com")
|
64
|
-
@request_stub1.
|
64
|
+
@request_stub1.instance_variable_set(:@responses, [@response1 = Response.new])
|
65
65
|
@request_stub2 = RequestStub.new(:get, "www.example.com")
|
66
|
-
@request_stub2.
|
66
|
+
@request_stub2.instance_variable_set(:@responses, [@response2 = Response.new])
|
67
67
|
@request_stub3 = RequestStub.new(:get, "www.example.org")
|
68
|
-
@request_stub3.
|
68
|
+
@request_stub3.instance_variable_set(:@responses, [@response3 = Response.new])
|
69
69
|
RequestRegistry.instance.register_request_stub(@request_stub1)
|
70
70
|
RequestRegistry.instance.register_request_stub(@request_stub2)
|
71
71
|
RequestRegistry.instance.register_request_stub(@request_stub3)
|
@@ -89,11 +89,21 @@ describe RequestSignature do
|
|
89
89
|
RequestSignature.new(:get, "www.example.com", :body => "abc").
|
90
90
|
should match(RequestProfile.new(:get, "www.example.com", :body => "abc"))
|
91
91
|
end
|
92
|
+
|
93
|
+
it "should match for body matching regexp" do
|
94
|
+
RequestSignature.new(:get, "www.example.com", :body => "abc").
|
95
|
+
should match(RequestProfile.new(:get, "www.example.com", :body => /^abc$/))
|
96
|
+
end
|
92
97
|
|
93
98
|
it "should not match for different bodies" do
|
94
99
|
RequestSignature.new(:get, "www.example.com", :body => "abc").
|
95
100
|
should_not match(RequestProfile.new(:get, "www.example.com", :body => "def"))
|
96
101
|
end
|
102
|
+
|
103
|
+
it "should not match for body not matching regexp" do
|
104
|
+
RequestSignature.new(:get, "www.example.com", :body => "xabc").
|
105
|
+
should_not match(RequestProfile.new(:get, "www.example.com", :body => /^abc$/))
|
106
|
+
end
|
97
107
|
|
98
108
|
it "should match if other has not specified body" do
|
99
109
|
RequestSignature.new(:get, "www.example.com", :body => "abc").
|
@@ -119,11 +129,21 @@ describe RequestSignature do
|
|
119
129
|
RequestSignature.new(:get, "www.example.com", :headers => {'Content-Type' => 'image/jpeg'}).
|
120
130
|
should match(RequestProfile.new(:get, "www.example.com", :headers => {'Content-Type' => 'image/jpeg'}))
|
121
131
|
end
|
132
|
+
|
133
|
+
it "should match for header values matching regexp" do
|
134
|
+
RequestSignature.new(:get, "www.example.com", :headers => {'Content-Type' => 'image/jpeg'}).
|
135
|
+
should match(RequestProfile.new(:get, "www.example.com", :headers => {'Content-Type' => %r{^image/jpeg$}}))
|
136
|
+
end
|
122
137
|
|
123
138
|
it "should not match for different values of the same header" do
|
124
139
|
RequestSignature.new(:get, "www.example.com", :headers => {'Content-Type' => 'image/jpeg'}).
|
125
140
|
should_not match(RequestProfile.new(:get, "www.example.com", :headers => {'Content-Type' => 'image/png'}))
|
126
141
|
end
|
142
|
+
|
143
|
+
it "should not match for header values not matching regexp" do
|
144
|
+
RequestSignature.new(:get, "www.example.com", :headers => {'Content-Type' => 'image/jpegx'}).
|
145
|
+
should_not match(RequestProfile.new(:get, "www.example.com", :headers => {'Content-Type' => %r{^image\/jpeg$}}))
|
146
|
+
end
|
127
147
|
|
128
148
|
it "should match if request has more headers than other" do
|
129
149
|
RequestSignature.new(:get, "www.example.com", :headers => {'Content-Type' => 'image/jpeg', 'Content-Length' => '8888'}).
|
@@ -165,6 +185,21 @@ describe RequestSignature do
|
|
165
185
|
should_not match(RequestProfile.new(:get, "www.example.com", :headers => {'A'=>'a'}))
|
166
186
|
end
|
167
187
|
|
188
|
+
it "should match if block given to profile evaluates on request to true" do
|
189
|
+
RequestSignature.new(:get, "www.example.com").
|
190
|
+
should match(RequestProfile.new(:get, "www.example.com").with { |request| true } )
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should not match if block given to profile evaluates on request to false" do
|
194
|
+
RequestSignature.new(:get, "www.example.com").
|
195
|
+
should_not match( RequestProfile.new(:get, "www.example.com").with { |request| false } )
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should pass self to the block when matching" do
|
199
|
+
signature = RequestSignature.new(:get, "www.example.com")
|
200
|
+
signature.should match(RequestProfile.new(:get, "www.example.com").with { |request| request == signature } )
|
201
|
+
end
|
202
|
+
|
168
203
|
end
|
169
204
|
|
170
205
|
end
|
data/spec/request_stub_spec.rb
CHANGED
@@ -28,6 +28,11 @@ describe RequestStub do
|
|
28
28
|
@request_stub.request_profile.headers.should == {'B' => 'b'}
|
29
29
|
end
|
30
30
|
|
31
|
+
it "should assign given block to request profile" do
|
32
|
+
@request_stub.with { |req| "block output" }
|
33
|
+
@request_stub.request_profile.with_block.call(nil).should == "block output"
|
34
|
+
end
|
35
|
+
|
31
36
|
end
|
32
37
|
|
33
38
|
describe "to_return" do
|
@@ -38,6 +43,46 @@ describe RequestStub do
|
|
38
43
|
@request_stub.response.status.should == 500
|
39
44
|
end
|
40
45
|
|
46
|
+
it "should assign responses with provided options" do
|
47
|
+
@request_stub.to_return([{:body => "abc"}, {:body => "def"}])
|
48
|
+
[@request_stub.response.body, @request_stub.response.body].should == ["abc", "def"]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "then" do
|
54
|
+
it "should return stub without any modifications, acting as syntactic sugar" do
|
55
|
+
@request_stub.then.should == @request_stub
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "response" do
|
60
|
+
|
61
|
+
it "should return responses in a sequence passed as array" do
|
62
|
+
@request_stub.to_return([{:body => "abc"}, {:body => "def"}])
|
63
|
+
@request_stub.response.body.should == "abc"
|
64
|
+
@request_stub.response.body.should == "def"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should repeat returning last response" do
|
68
|
+
@request_stub.to_return([{:body => "abc"}, {:body => "def"}])
|
69
|
+
@request_stub.response
|
70
|
+
@request_stub.response
|
71
|
+
@request_stub.response.body.should == "def"
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return responses in a sequence passed as comma separated params" do
|
75
|
+
@request_stub.to_return({:body => "abc"}, {:body => "def"})
|
76
|
+
@request_stub.response.body.should == "abc"
|
77
|
+
@request_stub.response.body.should == "def"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return responses declared in multiple to_return declarations" do
|
81
|
+
@request_stub.to_return({:body => "abc"}).to_return({:body => "def"})
|
82
|
+
@request_stub.response.body.should == "abc"
|
83
|
+
@request_stub.response.body.should == "def"
|
84
|
+
end
|
85
|
+
|
41
86
|
end
|
42
87
|
|
43
88
|
describe "to_raise" do
|
@@ -48,7 +93,85 @@ describe RequestStub do
|
|
48
93
|
@request_stub.response.raise_error_if_any
|
49
94
|
}.should raise_error(ArgumentError, "Exception from WebMock")
|
50
95
|
end
|
96
|
+
|
97
|
+
it "should assign sequence of responses with response with exception to be thrown" do
|
98
|
+
@request_stub.to_return(:body => "abc").then.to_raise(ArgumentError)
|
99
|
+
@request_stub.response.body.should == "abc"
|
100
|
+
lambda {
|
101
|
+
@request_stub.response.raise_error_if_any
|
102
|
+
}.should raise_error(ArgumentError, "Exception from WebMock")
|
103
|
+
end
|
51
104
|
|
105
|
+
it "should assign a list responses to be thrown in a sequence" do
|
106
|
+
@request_stub.to_raise(ArgumentError, IndexError)
|
107
|
+
lambda {
|
108
|
+
@request_stub.response.raise_error_if_any
|
109
|
+
}.should raise_error(ArgumentError, "Exception from WebMock")
|
110
|
+
lambda {
|
111
|
+
@request_stub.response.raise_error_if_any
|
112
|
+
}.should raise_error(IndexError, "Exception from WebMock")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should raise exceptions declared in multiple to_raise declarations" do
|
116
|
+
@request_stub.to_raise(ArgumentError).then.to_raise(IndexError)
|
117
|
+
lambda {
|
118
|
+
@request_stub.response.raise_error_if_any
|
119
|
+
}.should raise_error(ArgumentError, "Exception from WebMock")
|
120
|
+
lambda {
|
121
|
+
@request_stub.response.raise_error_if_any
|
122
|
+
}.should raise_error(IndexError, "Exception from WebMock")
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
describe "times" do
|
129
|
+
|
130
|
+
it "should give error if declared before any response declaration is declared" do
|
131
|
+
lambda {
|
132
|
+
@request_stub.times(3)
|
133
|
+
}.should raise_error("Invalid WebMock stub declaration. times(N) can be declared only after response declaration.")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should repeat returning last declared response declared number of times" do
|
137
|
+
@request_stub.to_return({:body => "abc"}).times(2).then.to_return({:body => "def"})
|
138
|
+
@request_stub.response.body.should == "abc"
|
139
|
+
@request_stub.response.body.should == "abc"
|
140
|
+
@request_stub.response.body.should == "def"
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should repeat raising last declared exception declared number of times" do
|
144
|
+
@request_stub.to_return({:body => "abc"}).times(2).then.to_return({:body => "def"})
|
145
|
+
@request_stub.response.body.should == "abc"
|
146
|
+
@request_stub.response.body.should == "abc"
|
147
|
+
@request_stub.response.body.should == "def"
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should repeat returning last declared sequence of responses declared number of times" do
|
151
|
+
@request_stub.to_return({:body => "abc"}, {:body => "def"}).times(2).then.to_return({:body => "ghj"})
|
152
|
+
@request_stub.response.body.should == "abc"
|
153
|
+
@request_stub.response.body.should == "def"
|
154
|
+
@request_stub.response.body.should == "abc"
|
155
|
+
@request_stub.response.body.should == "def"
|
156
|
+
@request_stub.response.body.should == "ghj"
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should return self" do
|
160
|
+
@request_stub.to_return({:body => "abc"}).times(1).should == @request_stub
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should raise error if argument is not integer" do
|
164
|
+
lambda {
|
165
|
+
@request_stub.to_return({:body => "abc"}).times("not number")
|
166
|
+
}.should raise_error("times(N) accepts integers >= 1 only")
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should raise error if argument is < 1" do
|
170
|
+
lambda {
|
171
|
+
@request_stub.to_return({:body => "abc"}).times(0)
|
172
|
+
}.should raise_error("times(N) accepts integers >= 1 only")
|
173
|
+
end
|
174
|
+
|
52
175
|
end
|
53
176
|
|
54
177
|
end
|