mockserver-client 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +23 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/README.md +200 -0
- data/Rakefile +11 -0
- data/lib/mockserver-client.rb +4 -0
- data/lib/mockserver/abstract_client.rb +112 -0
- data/lib/mockserver/mock_server_client.rb +50 -0
- data/lib/mockserver/model/array_of.rb +85 -0
- data/lib/mockserver/model/body.rb +56 -0
- data/lib/mockserver/model/delay.rb +34 -0
- data/lib/mockserver/model/enum.rb +47 -0
- data/lib/mockserver/model/expectation.rb +60 -0
- data/lib/mockserver/model/forward.rb +41 -0
- data/lib/mockserver/model/parameter.rb +46 -0
- data/lib/mockserver/model/request.rb +52 -0
- data/lib/mockserver/model/response.rb +39 -0
- data/lib/mockserver/model/times.rb +53 -0
- data/lib/mockserver/proxy_client.rb +9 -0
- data/lib/mockserver/utility_methods.rb +44 -0
- data/lib/mockserver/version.rb +5 -0
- data/mockserver-client.gemspec +31 -0
- data/spec/fixtures/forward_mockserver.json +7 -0
- data/spec/fixtures/incorrect_login_response.json +20 -0
- data/spec/fixtures/post_login_request.json +22 -0
- data/spec/fixtures/register_expectation.json +52 -0
- data/spec/fixtures/search_request.json +6 -0
- data/spec/fixtures/times_once.json +6 -0
- data/spec/mockserver/builder_spec.rb +83 -0
- data/spec/mockserver/mock_client_spec.rb +77 -0
- data/spec/mockserver/proxy_client_spec.rb +37 -0
- data/spec/spec_helper.rb +61 -0
- metadata +240 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
N2ZiZDY2MzcwMzQ5NGViNDc2YTQzYmY0NmYxMWI3ZjhkNGY5NzYyNQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OGVmMWM2ZWExZGVmZTk0YTU0MDI1MzU2ODM5YWNmMTNmYTY1M2NiMw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MzNkZDY5NWE2YTNlMGQ5YjQ1YWNiYjY0MGRlNTllYmZmMzRmN2YwNDYyOGM4
|
10
|
+
ODA2YzFiNzY0MGZjOWVlYTYyZGRhNzRmMzZlYmRkMzk4NjU3N2EyYzc5OGNk
|
11
|
+
NDBkMWFmMmRkNTk0YTI4NTFiOGM3MjMzMTc4NzQ4MTFiNzc2ZmQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGIyZDEwOTUwMjllYTc4YTgzNWEyYTdlMjAzZDg4MWUyMmExMDA3Nzg3ZTI1
|
14
|
+
ODNlNmJmYzcxODI4NjVkZWMxOTgzZGQyNmMxOGRiZGQzYmEyZWMwYWNhYzIw
|
15
|
+
MTMyMTk3ZTk5M2MzOTcwNjg4ZjliOTFlZWQ2OWM4MmY5ZTQ1MjQ=
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
tmp.log
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Style/LineLength:
|
2
|
+
Max: 180
|
3
|
+
Style/ClassAndModuleChildren:
|
4
|
+
EnforcedStyle: compact
|
5
|
+
Style/CyclomaticComplexity:
|
6
|
+
Enabled: false
|
7
|
+
Lint/LiteralInInterpolation:
|
8
|
+
Enabled: false
|
9
|
+
Style/MethodLength:
|
10
|
+
Enabled: false
|
11
|
+
Style/FileName:
|
12
|
+
Exclude:
|
13
|
+
- '**/mockserver-client.rb'
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# Mockserver Client
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/nayyara-samuel/mockserver-client.svg?branch=master)](https://travis-ci.org/nayyara-samuel/mockserver-client)
|
4
|
+
|
5
|
+
A Ruby client for [MockServer](http://www.mock-server.com) project. This client follows the Java client's fluent style closely by using Ruby blocks.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'mockserver-client'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install mockserver-client
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
The usage notes here compare the Java syntax with the Ruby DSL for the various actions the MockServer client/proxy client supports. The Ruby code here assumes you have included `MockServer` and `MockServer::Model::DSL` modules. So these lines should typically be at the top of your code:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
include MockServer
|
27
|
+
include MockServer::Model::DSL
|
28
|
+
```
|
29
|
+
|
30
|
+
### Create Expectations
|
31
|
+
|
32
|
+
##### Java
|
33
|
+
```java
|
34
|
+
|
35
|
+
new MockServerClient("localhost", 9999)
|
36
|
+
.when(
|
37
|
+
request()
|
38
|
+
.withMethod("POST")
|
39
|
+
.withPath("/login")
|
40
|
+
.withQueryStringParameters(
|
41
|
+
new Parameter("returnUrl", "/account")
|
42
|
+
)
|
43
|
+
.withCookies(
|
44
|
+
new Cookie("sessionId", "2By8LOhBmaW5nZXJwcmludCIlMDAzMW")
|
45
|
+
)
|
46
|
+
.withBody("{username: 'foo', password: 'bar'}"),
|
47
|
+
Times.exactly(1)
|
48
|
+
)
|
49
|
+
.respond(
|
50
|
+
response()
|
51
|
+
.withStatusCode(401)
|
52
|
+
.withHeaders(
|
53
|
+
new Header("Content-Type", "application/json; charset=utf-8"),
|
54
|
+
new Header("Cache-Control", "public, max-age=86400")
|
55
|
+
)
|
56
|
+
.withBody("{ message: 'incorrect username and password combination' }")
|
57
|
+
.withDelay(new Delay(TimeUnit.SECONDS, 1))
|
58
|
+
);
|
59
|
+
```
|
60
|
+
|
61
|
+
##### Ruby
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
|
65
|
+
client = MockServerClient.new('localhost', 9999)
|
66
|
+
expectation = expectation do |expectation|
|
67
|
+
expectation.request do |request|
|
68
|
+
request.method = 'POST'
|
69
|
+
request.path = '/login'
|
70
|
+
request.query_parameters << parameter('returnUrl', '/account')
|
71
|
+
request.cookies = [cookie('sessionId', '2By8LOhBmaW5nZXJwcmludCIlMDAzMW')]
|
72
|
+
request.body = exact("{username: 'foo', password: 'bar'}")
|
73
|
+
end
|
74
|
+
|
75
|
+
expectation.response do |response|
|
76
|
+
response.status_code = 401
|
77
|
+
response.headers << header('Content-Type', 'application/json; charset=utf-8')
|
78
|
+
response.headers << header('Cache-Control', 'public, max-age=86400')
|
79
|
+
response.body = body("{ message: 'incorrect username and password combination' }")
|
80
|
+
response.delay = delay_by(:SECONDS, 1)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
client.register(expectation)
|
84
|
+
```
|
85
|
+
|
86
|
+
### Clear & Reset Server
|
87
|
+
|
88
|
+
##### Java
|
89
|
+
|
90
|
+
```java
|
91
|
+
mockServerClient.clear(
|
92
|
+
request()
|
93
|
+
.withMethod("POST")
|
94
|
+
.withPath("/login")
|
95
|
+
);
|
96
|
+
mockServerClient.reset();
|
97
|
+
```
|
98
|
+
|
99
|
+
##### Ruby
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
client.clear(request('POST', '/login'))
|
103
|
+
client.reset
|
104
|
+
```
|
105
|
+
### Verifying Behavior
|
106
|
+
|
107
|
+
##### Java
|
108
|
+
|
109
|
+
```java
|
110
|
+
new ProxyClient("localhost", 9090).verify(
|
111
|
+
request()
|
112
|
+
.withMethod("POST")
|
113
|
+
.withPath("/login")
|
114
|
+
.withBody(exact("{username: 'foo', password: 'bar'}"))
|
115
|
+
.withCookies(
|
116
|
+
new Cookie("sessionId", ".*")
|
117
|
+
),
|
118
|
+
Times.exactly(1)
|
119
|
+
);
|
120
|
+
```
|
121
|
+
|
122
|
+
##### Ruby
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
|
126
|
+
# Not providing times here because the default is exactly(1) i.e. the second argument to verify method
|
127
|
+
ProxyClient.new('localhost', 9090).verify(request(:POST, '/login') do |request|
|
128
|
+
request.body = exact("{username: 'foo', password: 'bar'}")
|
129
|
+
request.cookies = [cookie("sessionId", ".*")]
|
130
|
+
end)
|
131
|
+
```
|
132
|
+
|
133
|
+
|
134
|
+
### Analyzing Behavior
|
135
|
+
|
136
|
+
##### Java
|
137
|
+
|
138
|
+
```java
|
139
|
+
new ProxyClient("localhost", 9090).dumpToLogAsJava(
|
140
|
+
request()
|
141
|
+
.withMethod("POST")
|
142
|
+
.withPath("/login")
|
143
|
+
);
|
144
|
+
```
|
145
|
+
|
146
|
+
##### Ruby
|
147
|
+
```ruby
|
148
|
+
# Second argument is true to set output to Java; false to set output to default JSON (default)
|
149
|
+
ProxyClient.new('localhost', 9090).dump_log(request(:POST, '/login'), true)
|
150
|
+
```
|
151
|
+
|
152
|
+
## Complete Ruby DSL
|
153
|
+
The DSL is provided via the `MockServer::Model::DSL` module. Include this module in your code to make the DSL available.
|
154
|
+
|
155
|
+
Request
|
156
|
+
* **request**: Used to build a request object. If a block is passed, will configure request first and then return the configured request. Example: `request(:POST, '/login') {|r| r.headers << header("Content-Type", "application/json")}`.
|
157
|
+
* **http_request**: Alias for `request`.
|
158
|
+
|
159
|
+
Body
|
160
|
+
* **body**: Create a string body object (use in a response). Example: `body("unaccepted")`.
|
161
|
+
* **exact**: Create a body of type `EXACT`. Example: `exact('{"reason": "unauthorized"}')`.
|
162
|
+
* **regex**: Create a body of type `REGEX`. Example: `regex('username[a-z]{4}')`.
|
163
|
+
* **xpath**: Used to create a body of type `XPATH`. Example: `xpath("/element[key = 'some_key' and value = 'some_value']")`.
|
164
|
+
* **parameterized**; Create a body to type `PARAMETERS`. Example `parameterized(parameter('someValue', 1, 2), parameter('otherValue', 4, 5))`.
|
165
|
+
|
166
|
+
Parameters
|
167
|
+
* **parameter**: Create a generic parameter. Example: `parameter('key', 'value1' , 'value2')`.
|
168
|
+
* **cookie**: Create a cookie (same as `parameter` above but exists as syntactic sugar). Example: `cookie('sessionId', 'sessionid1ldj')`.
|
169
|
+
* **header**: Create a header (same as `parameter` above but exists as syntactic sugar). Example: `header('Content-Type', 'application/json')`.
|
170
|
+
|
171
|
+
Forward
|
172
|
+
* **forward**: Create a forwarding response. If a block is passed, will configure forward response first and then return the configured object. Example: `forward {|f| f.scheme = 'HTTPS' }`.
|
173
|
+
* **http_forward**: Alias for `forward`.
|
174
|
+
|
175
|
+
Response
|
176
|
+
* **response**: Create a response object. If a block is passed, will configure response first and then return the configured response. Example: `response {|r| r.status_code = 201 }`.
|
177
|
+
* **http_response**: Alias for `response`.
|
178
|
+
|
179
|
+
Delay
|
180
|
+
* **delay_by**. Create a delay object in a response. Example : `delay_by(:MICROSECONDS, 20)`.
|
181
|
+
|
182
|
+
Times (used in Expectation)
|
183
|
+
* **times**: Create an 'times' object. If a block is passed, will configure the object first before returning it. Example: `times {|t| t.unlimited = false }`.
|
184
|
+
* **unlimited**: Create an object with unlimited repeats. Example: `unlimited()`. (No parameters).
|
185
|
+
* **once**. Create an object that repeats only once. Example: `once()`. (No parameters).
|
186
|
+
* **exactly**. Create an object that repeats exactly the number of times specified. Example: `exactly(2)`.
|
187
|
+
* **at_least**: Create an object that repeats at least the given number of times. (Use in verify). Example: `at_least(2)`.
|
188
|
+
|
189
|
+
Expectation (use in register)
|
190
|
+
* **expectation**: Create an expectation object. If a block is passed, will configure the object first before returning it. Example: `expectation {|e| e.request {|r| r.path = "index.html} }`.
|
191
|
+
Getter methods for `request`, `response` and `forward` methods will optionally accept a block. If block is passed object is configured before it is returned. The attribute `times` has conventional getter and setter methods.
|
192
|
+
|
193
|
+
## Contributing
|
194
|
+
|
195
|
+
1. Fork it ( https://github.com/[my-github-username]/mockserver-client/fork )
|
196
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
197
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
198
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
199
|
+
5. Create a new Pull Request
|
200
|
+
6. **IMPORTANT**: Keep code coverage high. Preferably above 95%. This project uses SimpleCov for code coverage which reports percentage coverage.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'rspec/mocks/version'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
RuboCop::RakeTask.new(:rubocop)
|
9
|
+
|
10
|
+
desc 'Main task for this project to ensure the project passes build'
|
11
|
+
task default: [:rubocop, :spec, :build]
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rest-client'
|
3
|
+
require 'logging_factory'
|
4
|
+
require_relative './model/times'
|
5
|
+
require_relative './model/request'
|
6
|
+
|
7
|
+
# An abstract client for making requests supported by mock and proxy client.
|
8
|
+
# @author Nayyara Samuel(mailto: nayyara.samuel@opower.com)
|
9
|
+
#
|
10
|
+
module MockServer
|
11
|
+
HTTP_REQUEST = 'httpRequest'
|
12
|
+
HTTP_RESPONSE = 'httpResponse'
|
13
|
+
HTTP_FORWARD = 'httpForward'
|
14
|
+
HTTP_TIMES = 'times'
|
15
|
+
|
16
|
+
RESET_ENDPOINT = '/reset'
|
17
|
+
CLEAR_ENDPOINT = '/clear'
|
18
|
+
RETRIEVE_ENDPOINT = '/retrieve'
|
19
|
+
DUMP_LOG_ENDPOINT = '/dumpToLog'
|
20
|
+
|
21
|
+
# An abstract client for making requests supported by mock and proxy client.
|
22
|
+
class AbstractClient
|
23
|
+
include Model::DSL
|
24
|
+
include Model
|
25
|
+
|
26
|
+
attr_accessor :logger
|
27
|
+
|
28
|
+
def initialize(host, port)
|
29
|
+
fail 'Cannot instantiate AbstractClient class. You must subclass it.' if self.class == AbstractClient
|
30
|
+
fail 'Host/port must not be nil' unless host && port
|
31
|
+
@base = RestClient::Resource.new("http://#{host}:#{port}")
|
32
|
+
@logger = ::LoggingFactory::DEFAULT_FACTORY.log(self.class)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Clear all expectations with the given request
|
36
|
+
# @param request [Request] the request to use to clear an expectation
|
37
|
+
# @return [Object] the response from the clear action
|
38
|
+
def clear(request)
|
39
|
+
request = prepare_hash(HTTP_REQUEST => Request.new(request))
|
40
|
+
url = CLEAR_ENDPOINT
|
41
|
+
|
42
|
+
logger.debug("Clearing expectation with request: #{request}")
|
43
|
+
logger.debug("URL: #{url}. Payload: #{request.to_hash}")
|
44
|
+
|
45
|
+
response = @base[url].put(request.to_json, content_type: :json)
|
46
|
+
logger.debug("Got clear response: #{response.code}")
|
47
|
+
parse_response(response)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Reset the mock server clearing all expectations previously registered
|
51
|
+
# @return [Object] the response from the reset action
|
52
|
+
def reset
|
53
|
+
request = {}
|
54
|
+
url = RESET_ENDPOINT
|
55
|
+
|
56
|
+
logger.debug('Resetting mockserver')
|
57
|
+
logger.debug("URL: #{url}. Payload: #{request.to_hash}")
|
58
|
+
|
59
|
+
response = @base[url].put(request.to_json)
|
60
|
+
logger.debug("Got reset response: #{response.code}")
|
61
|
+
parse_response(response)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Retrieve the list of requests that have been processed by the server
|
65
|
+
# @param request [Request] to filter requests
|
66
|
+
# @return [Object] the list of responses processed by the server
|
67
|
+
def retrieve(request = nil)
|
68
|
+
request = request ? prepare_hash(HTTP_REQUEST => Request.new(request)) : {}
|
69
|
+
url = RETRIEVE_ENDPOINT
|
70
|
+
|
71
|
+
logger.debug('Retrieving request list from mockserver')
|
72
|
+
logger.debug("URL: #{url}. Payload: #{request.to_hash}")
|
73
|
+
|
74
|
+
response = @base[RETRIEVE_ENDPOINT].put(request.to_json)
|
75
|
+
logger.debug("Got retrieve response: #{response.code}")
|
76
|
+
parse_response(response)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Request to dump logs to file
|
80
|
+
# @param java [Boolean] true to dump as Java code; false to dump as JSON
|
81
|
+
# @return [Object] the list of responses processed by the server
|
82
|
+
def dump_log(request = nil, java = false)
|
83
|
+
type_params = java ? '?type=java' : ''
|
84
|
+
url = "#{DUMP_LOG_ENDPOINT}#{type_params}"
|
85
|
+
request = Request.new(request) || {}
|
86
|
+
|
87
|
+
logger.debug('Sending dump log request to mockserver')
|
88
|
+
logger.debug("URL: #{url}. Payload: #{request.to_hash}")
|
89
|
+
|
90
|
+
response = @base[url].put(request.to_json)
|
91
|
+
logger.debug("Got dump to log response: #{response.code}")
|
92
|
+
parse_response(response)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Verify that the given request is called the number of times expected
|
96
|
+
# @param request [Request] to filter requests
|
97
|
+
# @param times [Times] expected number of times
|
98
|
+
# @return [Object] the list of responses processed by the server that match the request
|
99
|
+
def verify(request, times = exactly(1))
|
100
|
+
logger.debug('Sending query for verify to mockserver')
|
101
|
+
results = retrieve(request)
|
102
|
+
|
103
|
+
# Reusing the times model here so interpreting values here
|
104
|
+
num_times = times.remaining_times
|
105
|
+
is_exact = !times.unlimited
|
106
|
+
|
107
|
+
fulfilled = is_exact ? (num_times == results.size) : (num_times <= results.size)
|
108
|
+
fail "Expected request to be present: [#{num_times}] (#{is_exact ? 'exactly' : 'at least'}). But found: [#{results.size}]" unless fulfilled
|
109
|
+
results
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative './model/expectation'
|
3
|
+
require_relative './abstract_client'
|
4
|
+
require_relative './utility_methods'
|
5
|
+
|
6
|
+
#
|
7
|
+
# The client used to interact with the mock server.
|
8
|
+
# @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
|
9
|
+
#
|
10
|
+
module MockServer
|
11
|
+
EXPECTATION_ENDPOINT = '/expectation'
|
12
|
+
|
13
|
+
# The client used to interact with the mock server.
|
14
|
+
class MockServerClient < AbstractClient
|
15
|
+
include Model
|
16
|
+
include UtilityMethods
|
17
|
+
|
18
|
+
# Registers an expectation with the mockserver
|
19
|
+
# @param expectation [Expectation] the expectation to create the request from
|
20
|
+
# @return [Object] the response from the register action
|
21
|
+
def register(expectation)
|
22
|
+
fail 'Expectation passed in is not valid type' unless expectation.is_a?(Expectation)
|
23
|
+
url = EXPECTATION_ENDPOINT
|
24
|
+
request = create_expectation_request(expectation)
|
25
|
+
|
26
|
+
logger.debug('Registering new expectation')
|
27
|
+
logger.debug("URL: #{url} Payload: #{request.to_hash}")
|
28
|
+
|
29
|
+
response = @base[url].put(request.to_json, content_type: :json)
|
30
|
+
logger.debug("Got register response: #{response.code}")
|
31
|
+
parse_response(response)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Create an expecation request to send to the expectation endpoint of
|
37
|
+
# @param expectation [Expectation] the expectation to create the request from
|
38
|
+
# @return [Hash] a hash representing the request to use in registering an expectation with the mock server
|
39
|
+
def create_expectation_request(expectation)
|
40
|
+
expectation_request = prepare_hash(HTTP_REQUEST => expectation.request,
|
41
|
+
HTTP_RESPONSE => expectation.response,
|
42
|
+
HTTP_FORWARD => expectation.forward,
|
43
|
+
HTTP_TIMES => expectation.times)
|
44
|
+
|
45
|
+
logger.debug("Expectation JSON: #{expectation_request.to_json}")
|
46
|
+
fail "You can only set either of #{[HTTP_RESPONSE, HTTP_FORWARD]}. But not both" if expectation_request[HTTP_RESPONSE] && expectation_request[HTTP_FORWARD]
|
47
|
+
expectation_request
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|