pinch_hitter 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog CHANGED
@@ -3,3 +3,6 @@ Initial release of the gem
3
3
 
4
4
  === Release 0.2 / 2013-1-27
5
5
  Switch json to do overrides by key rather than path (like xml)
6
+
7
+ === Release 0.3 / 2013-3-22
8
+ Add ability to register modules for custom message handling
@@ -41,3 +41,15 @@ end
41
41
  Then /^I see a definition with a "(.*?)" of "(.*?)"$/ do |key, value|
42
42
  @response.body.to_s.should == messages.load(:glossary, { key => value }).squish
43
43
  end
44
+
45
+ Given(/^I want to do some fancy processing$/) do
46
+ mock.register_module('/service', XmlParser)
47
+ end
48
+
49
+ When(/^I query my service with (.*?)$/) do |request|
50
+ @response = app.post '/service', "<request>#{request}</request>"
51
+ end
52
+
53
+ Then(/^I see (.*?) in the service response$/) do |response|
54
+ @response.body.to_s.should == "<response>#{response}</response>"
55
+ end
@@ -0,0 +1,10 @@
1
+ module XmlParser
2
+ def respond_to(message)
3
+ if message.include? "ABC"
4
+ result = "123"
5
+ elsif message.include? "DEF"
6
+ result = "Comedy Jam"
7
+ end
8
+ "<response>#{result}</response>"
9
+ end
10
+ end
@@ -25,3 +25,13 @@ Feature: Test WS replay
25
25
  Given I want to lookup a definition with a "ID" of "FOO"
26
26
  When I query the glossary
27
27
  Then I see a definition with a "ID" of "FOO"
28
+
29
+ Scenario Outline: Replay with custom module
30
+ Given I want to do some fancy processing
31
+ When I query my service with <request>
32
+ Then I see <response> in the service response
33
+
34
+ Examples:
35
+ |request| response |
36
+ |ABC | 123 |
37
+ |DEF | Comedy Jam |
data/lib/pinch_hitter.rb CHANGED
@@ -30,4 +30,8 @@ module PinchHitter
30
30
  @session.post "/store?endpoint=#{endpoint}", content
31
31
  end
32
32
 
33
+ def register_module(endpoint, handler)
34
+ @session.post "/register_module?endpoint=#{endpoint}", Marshal.dump(handler)
35
+ end
36
+
33
37
  end
@@ -1,15 +1,12 @@
1
1
  require 'pinch_hitter/message/json'
2
2
 
3
- module PinchHitter
4
- module Message
5
- module ContentType
6
- include Json
3
+ module PinchHitter::Message
4
+ module ContentType
5
+ include Json
7
6
 
8
- def determine_content_type(message)
9
- return "application/json" if valid_json? message
10
- "text/xml"
11
- end
12
-
7
+ def determine_content_type(message)
8
+ return "application/json" if valid_json? message
9
+ "text/xml"
13
10
  end
14
11
  end
15
12
  end
@@ -1,54 +1,50 @@
1
- module PinchHitter
2
- module Message
3
- module Json
4
- def json_message(file, overrides={})
5
- json_file = load_json_file file
6
- replace_json json_file, overrides
7
- end
1
+ module PinchHitter::Message
2
+ module Json
3
+ def json_message(file, overrides={})
4
+ json_file = load_json_file file
5
+ replace_json json_file, overrides
6
+ end
8
7
 
9
- def valid_json?(json)
10
- begin
11
- JSON.parse json
12
- return true
13
- rescue
14
- return false
15
- end
8
+ def valid_json?(json)
9
+ begin
10
+ JSON.parse json
11
+ return true
12
+ rescue
13
+ return false
16
14
  end
15
+ end
17
16
 
18
- private
19
- def load_json_file(filename)
20
- IO.read filename
21
- end
17
+ private
18
+ def load_json_file(filename)
19
+ IO.read filename
20
+ end
22
21
 
23
- def replace_json(content, overrides={})
24
- return content if overrides.empty?
25
- doc = JSON.parse(content)
26
- overrides.each do |key, value|
27
- hash = find_nested_hash(doc, key)
28
- if has_key(hash, key)
29
- hash[key] = value
30
- end
22
+ def replace_json(content, overrides={})
23
+ return content if overrides.empty?
24
+ doc = JSON.parse(content)
25
+ overrides.each do |key, value|
26
+ hash = find_nested_hash(doc, key)
27
+ if has_key(hash, key)
28
+ hash[key] = value
31
29
  end
32
- doc.to_s
33
30
  end
31
+ doc.to_s
32
+ end
34
33
 
35
- def find_nested_hash(parent, key)
36
- return parent if has_key(parent, key)
37
- return nil unless parent.respond_to? :each
38
-
39
- found = nil
40
- parent.find do |parent_key, child|
41
- found = find_nested_hash(child, key)
42
- end
43
- found
44
- end
34
+ def find_nested_hash(parent, key)
35
+ return parent if has_key(parent, key)
36
+ return nil unless parent.respond_to? :each
45
37
 
46
- def has_key(hash, key)
47
- hash.respond_to?(:key?) && hash.key?(key)
38
+ found = nil
39
+ parent.find do |parent_key, child|
40
+ found = find_nested_hash(child, key)
48
41
  end
42
+ found
43
+ end
49
44
 
50
-
51
-
45
+ def has_key(hash, key)
46
+ hash.respond_to?(:key?) && hash.key?(key)
52
47
  end
48
+
53
49
  end
54
50
  end
@@ -3,32 +3,30 @@ require 'pinch_hitter/message/json'
3
3
  require 'pinch_hitter/message/content_type'
4
4
  require 'pinch_hitter/core_ext/string'
5
5
 
6
- module PinchHitter
7
- module Message
6
+ module PinchHitter::Message
8
7
  class MessageStore
9
- include Xml
10
- include Json
11
- include ContentType
8
+ include Xml
9
+ include Json
10
+ include ContentType
12
11
 
13
- attr_accessor :message_directory
12
+ attr_accessor :message_directory
14
13
 
15
- def initialize(message_directory)
16
- @message_directory = message_directory
17
- end
18
-
19
- def load(file, overrides={})
20
- filename = find_filename file
21
- if filename =~ /xml$/
22
- xml_message filename, overrides
23
- else
24
- json_message filename, overrides
25
- end
26
- end
14
+ def initialize(message_directory)
15
+ @message_directory = message_directory
16
+ end
27
17
 
28
- def find_filename(file)
29
- Dir["#{message_directory}/#{file}*"].first
18
+ def load(file, overrides={})
19
+ filename = find_filename file
20
+ if filename =~ /xml$/
21
+ xml_message filename, overrides
22
+ else
23
+ json_message filename, overrides
30
24
  end
25
+ end
31
26
 
27
+ def find_filename(file)
28
+ Dir["#{message_directory}/#{file}*"].first
32
29
  end
30
+
33
31
  end
34
32
  end
@@ -1,37 +1,35 @@
1
1
  require 'nokogiri'
2
2
 
3
- module PinchHitter
4
- module Message
5
- module Xml
6
- def xml_message(file, overrides={})
7
- xml = load_xml_file(file)
8
- overrides.each do |key, text|
9
- replace_xml(xml, key, text)
10
- end
11
- xml.to_s
3
+ module PinchHitter::Message
4
+ module Xml
5
+ def xml_message(file, overrides={})
6
+ xml = load_xml_file(file)
7
+ overrides.each do |key, text|
8
+ replace_xml(xml, key, text)
12
9
  end
10
+ xml.to_s
11
+ end
13
12
 
14
- private
15
- def load_xml_file(filename)
16
- Nokogiri::XML File.open filename
17
- end
13
+ private
14
+ def load_xml_file(filename)
15
+ Nokogiri::XML File.open filename
16
+ end
18
17
 
19
- def replace_xml(xml, key, text)
20
- parts = key.split('@')
21
- tag = find_node xml, parts.first
18
+ def replace_xml(xml, key, text)
19
+ parts = key.split('@')
20
+ tag = find_node xml, parts.first
22
21
 
23
- if parts.length == 1
24
- #match text node
25
- tag.content = text
26
- else
27
- #match attribute
28
- tag[parts.last] = text
29
- end
22
+ if parts.length == 1
23
+ #match text node
24
+ tag.content = text
25
+ else
26
+ #match attribute
27
+ tag[parts.last] = text
30
28
  end
29
+ end
31
30
 
32
- def find_node(xml, tag)
33
- xml.at_xpath("//#{tag}", xml.collect_namespaces)
34
- end
31
+ def find_node(xml, tag)
32
+ xml.at_xpath("//#{tag}", xml.collect_namespaces)
35
33
  end
36
34
  end
37
35
  end
@@ -0,0 +1,45 @@
1
+ require_relative 'message_queue'
2
+
3
+ module PinchHitter::Service
4
+ class EndpointHandlers
5
+ def handlers
6
+ @handlers ||= {}
7
+ end
8
+
9
+ def store_message(endpoint, body)
10
+ handler_for(endpoint) << body.squish
11
+ end
12
+
13
+ def respond_to(endpoint='/', request='')
14
+ message = handler_for(endpoint).respond_to(request)
15
+ message.squish if message
16
+ end
17
+
18
+ def handler_for(endpoint='/')
19
+ handlers[normalize(endpoint)] || store_handler(endpoint)
20
+ end
21
+
22
+ def register_module(endpoint, mod)
23
+ handler = Object.new
24
+ handler.extend(mod)
25
+ store_handler(endpoint, handler)
26
+ end
27
+
28
+ def store_handler(endpoint, handler=MessageQueue.new)
29
+ handlers[normalize(endpoint)] = handler
30
+ end
31
+
32
+ def normalize(endpoint)
33
+ return endpoint if endpoint =~ /^\//
34
+ "/#{endpoint}"
35
+ end
36
+
37
+ def reset
38
+ handlers.values.each do |handler|
39
+ if(handler.respond_to? :reset)
40
+ handler.reset
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ module PinchHitter::Service
2
+ class MessageQueue < Array
3
+ def respond_to(msg=nil)
4
+ shift
5
+ end
6
+
7
+ def reset
8
+ clear
9
+ end
10
+ end
11
+ end
@@ -3,59 +3,66 @@ require 'sinatra/base'
3
3
  require 'nokogiri'
4
4
  require 'json'
5
5
 
6
- require 'pinch_hitter/service/response_queues'
6
+ require 'pinch_hitter/service/endpoint_handlers'
7
7
  require 'pinch_hitter/message/content_type'
8
8
 
9
- module PinchHitter
10
- module Service
11
- class ReplayWs < Sinatra::Base
12
- include PinchHitter::Message::ContentType
13
-
14
- configure do
15
- @@responses = ResponseQueues.new
16
- #SOAP expects a mime_type of text/xml
17
- mime_type :xml, "text/xml"
18
- mime_type :json, "application/json"
19
- end
20
-
21
- post '/reset' do
22
- @@responses.reset
23
- status 200
24
- end
25
-
26
- post '/store/*' do
27
- store "/#{params[:splat].first}", request.body.read
28
- status 200
29
- end
30
-
31
- post '/store' do
32
- store request["endpoint"], request.body.read
33
- status 200
34
- end
35
-
36
- post '/respond' do
37
- respond nil
38
- end
39
-
40
- get '/*' do
41
- respond params[:splat].first
42
- end
43
-
44
- post '/*' do
45
- respond params[:splat].first
46
- end
47
-
48
- def store(endpoint='/', message=nil)
49
- @@responses.store endpoint, message
50
- end
51
-
52
- def respond(endpoint='/')
53
- message = @@responses.retrieve endpoint
54
- content_type determine_content_type message
55
- puts "No message found for #{endpoint}" unless message
56
- message
57
- end
9
+ module PinchHitter::Service
10
+ class ReplayWs < Sinatra::Base
11
+ include PinchHitter::Message::ContentType
58
12
 
13
+ configure do
14
+ @@handlers = EndpointHandlers.new
15
+ #SOAP expects a mime_type of text/xml
16
+ mime_type :xml, "text/xml"
17
+ mime_type :json, "application/json"
59
18
  end
19
+
20
+ post '/reset' do
21
+ @@handlers.reset
22
+ status 200
23
+ end
24
+
25
+ post '/store/*' do
26
+ store "/#{params[:splat].first}", request.body.read
27
+ status 200
28
+ end
29
+
30
+ post '/store' do
31
+ store request["endpoint"], request.body.read
32
+ status 200
33
+ end
34
+
35
+ post '/register_module' do
36
+ register_module request["endpoint"], request.body.read
37
+ status 200
38
+ end
39
+
40
+ post '/respond' do
41
+ respond nil
42
+ end
43
+
44
+ get '/*' do
45
+ respond params[:splat].first
46
+ end
47
+
48
+ post '/*' do
49
+ respond params[:splat].first, request.body.read
50
+ end
51
+
52
+ def store(endpoint='/', message=nil)
53
+ @@handlers.store_message endpoint, message
54
+ end
55
+
56
+ def respond(endpoint='/', request=nil)
57
+ message = @@handlers.respond_to endpoint, request
58
+ content_type determine_content_type message
59
+ puts "No message found for #{endpoint}" unless message
60
+ message
61
+ end
62
+
63
+ def register_module(endpoint='/', mod='')
64
+ @@handlers.register_module endpoint, Marshal.load(mod)
65
+ end
66
+
60
67
  end
61
68
  end
@@ -2,32 +2,27 @@ require 'rack'
2
2
  require 'webrick'
3
3
  require 'pinch_hitter/service/replay_ws'
4
4
 
5
- module PinchHitter
6
- module Service
7
- module Runner
8
-
9
- def start_service(host, port, timeout=10)
10
- Thread.abort_on_exception = true
11
- @app = PinchHitter::Service::ReplayWs.new
12
- @replay_service = Thread.new do
13
- Rack::Handler::WEBrick.run @app, :Host => host, :Port => port
14
- end
15
- wait_for_replay(timeout)
16
- end
5
+ module PinchHitter::Service::Runner
6
+ def start_service(host, port, timeout=10)
7
+ Thread.abort_on_exception = true
8
+ @app = PinchHitter::Service::ReplayWs.new
9
+ @replay_service = Thread.new do
10
+ Rack::Handler::WEBrick.run @app, :Host => host, :Port => port
11
+ end
12
+ wait_for_replay(timeout)
13
+ end
17
14
 
18
- def stop_service
19
- @replay_service.kill
20
- end
15
+ def stop_service
16
+ @replay_service.kill
17
+ end
21
18
 
22
- private
23
- def wait_for_replay(timeout)
24
- end_time = ::Time.now + timeout
25
- until ::Time.now > end_time
26
- return if @replay_service.stop?
27
- sleep 0.25
28
- end
29
- raise 'Timed out waiting for replay service to start'
30
- end
19
+ private
20
+ def wait_for_replay(timeout)
21
+ end_time = ::Time.now + timeout
22
+ until ::Time.now > end_time
23
+ sleep 0.25
24
+ return if @replay_service.stop?
31
25
  end
26
+ raise 'Timed out waiting for replay service to start'
32
27
  end
33
28
  end
@@ -1,3 +1,3 @@
1
1
  module PinchHitter
2
- VERSION = "0.2"
2
+ VERSION = "0.3"
3
3
  end
@@ -0,0 +1,21 @@
1
+ module MessageAssertions
2
+ def xml_message
3
+ message = %Q{<?xml version="1.0" encoding="UTF-8"?>
4
+ <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:replay="http://www.leandog.com/replay">
5
+ <env:Body>
6
+ <replay:Response>BARK!</replay:Response>
7
+ </env:Body>
8
+ </env:Envelope>}
9
+ end
10
+
11
+ def yml_message
12
+ %Q~{"menu": {
13
+ "id": "file",
14
+ "value": "File"
15
+ }}~
16
+ end
17
+
18
+ def assert_received(message)
19
+ assert_equal message.gsub(/\n\s*/, ''), last_response.body.strip
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'minitest/autorun'
4
+ require 'pinch_hitter/service/endpoint_handlers'
5
+
6
+
7
+ class TestEndpointHandlers < MiniTest::Unit::TestCase
8
+
9
+ def setup
10
+ @handlers = PinchHitter::Service::EndpointHandlers.new
11
+ end
12
+
13
+ def json
14
+ %Q{{"key"::"value"}}
15
+ end
16
+
17
+ def test_message_queue
18
+ @handlers.store_message 'endpoint', json
19
+ handler = @handlers.handler_for 'endpoint'
20
+ assert_instance_of(PinchHitter::Service::MessageQueue, handler)
21
+ assert_equal json, handler.respond_to
22
+ end
23
+
24
+ def test_defaults_to_message_queue
25
+ @handlers.store_message 'endpoint', json
26
+ assert_equal json, @handlers.respond_to('endpoint')
27
+ end
28
+
29
+ def test_register_module
30
+ @handlers.register_module('endpoint', TestModule)
31
+ assert_equal "THIS IS A TEST", @handlers.respond_to('endpoint')
32
+ end
33
+
34
+ def test_reset_clears_message_queues
35
+ @handlers.store_message 'endpoint', json
36
+ @handlers.reset
37
+ assert_nil @handlers.respond_to('endpoint')
38
+ end
39
+
40
+ def test_reset_does_not_clear_modules
41
+ @handlers.register_module('endpoint', TestModule)
42
+ @handlers.reset
43
+ assert_equal "THIS IS A TEST", @handlers.respond_to('endpoint')
44
+ end
45
+
46
+ def test_register_module_with_marshalling
47
+ mod = Marshal.dump(TestModule)
48
+ @handlers.register_module('endpoint', Marshal.load(mod))
49
+ assert_equal "THIS IS A TEST", @handlers.respond_to('endpoint')
50
+ end
51
+
52
+
53
+ module TestModule
54
+ def respond_to(msg)
55
+ test_message
56
+ end
57
+ def test_message
58
+ "THIS IS A TEST"
59
+ end
60
+ end
61
+ end
data/test/test_service.rb CHANGED
@@ -4,9 +4,11 @@ require 'pinch_hitter/service/replay_ws'
4
4
  require 'minitest/autorun'
5
5
  require 'rack/test'
6
6
  require 'nokogiri'
7
+ require_relative 'message_assertions'
7
8
 
8
9
  class TestService < MiniTest::Unit::TestCase
9
10
  include Rack::Test::Methods
11
+ include MessageAssertions
10
12
 
11
13
  def app
12
14
  PinchHitter::Service::ReplayWs
@@ -91,23 +93,18 @@ class TestService < MiniTest::Unit::TestCase
91
93
  assert_equal '', last_response.body
92
94
  end
93
95
 
94
- def xml_message
95
- message = %Q{<?xml version="1.0" encoding="UTF-8"?>
96
- <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:replay="http://www.leandog.com/replay">
97
- <env:Body>
98
- <replay:Response>BARK!</replay:Response>
99
- </env:Body>
100
- </env:Envelope>}
101
- end
96
+ def test_module
97
+ post '/register_module?endpoint=stuff', Marshal.dump(TestModule)
98
+ post '/stuff', ''
102
99
 
103
- def yml_message
104
- %Q~{"menu": {
105
- "id": "file",
106
- "value": "File"
107
- }}~
100
+ assert_received xml_message
108
101
  end
109
102
 
110
- def assert_received(message)
111
- assert_equal message.gsub(/\n\s*/, ''), last_response.body.strip
103
+ module TestModule
104
+ include MessageAssertions
105
+ def respond_to(message)
106
+ xml_message
107
+ end
112
108
  end
109
+
113
110
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pinch_hitter
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.2'
4
+ version: '0.3'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-28 00:00:00.000000000 Z
12
+ date: 2013-03-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -161,6 +161,7 @@ files:
161
161
  - features/step_definitions/test_replay_steps.rb
162
162
  - features/support/env.rb
163
163
  - features/support/mock_web_service.rb
164
+ - features/support/xml_parser.rb
164
165
  - features/test_replay.feature
165
166
  - lib/pinch_hitter.rb
166
167
  - lib/pinch_hitter/core_ext/string.rb
@@ -168,11 +169,14 @@ files:
168
169
  - lib/pinch_hitter/message/json.rb
169
170
  - lib/pinch_hitter/message/message_store.rb
170
171
  - lib/pinch_hitter/message/xml.rb
172
+ - lib/pinch_hitter/service/endpoint_handlers.rb
173
+ - lib/pinch_hitter/service/message_queue.rb
171
174
  - lib/pinch_hitter/service/replay_ws.rb
172
- - lib/pinch_hitter/service/response_queues.rb
173
175
  - lib/pinch_hitter/service/runner.rb
174
176
  - lib/pinch_hitter/version.rb
175
177
  - pinch_hitter.gemspec
178
+ - test/message_assertions.rb
179
+ - test/test_endpoint_handlers.rb
176
180
  - test/test_json_message.rb
177
181
  - test/test_message.rb
178
182
  - test/test_pinch_hitter.rb
@@ -192,7 +196,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
192
196
  version: '0'
193
197
  segments:
194
198
  - 0
195
- hash: 2707663932851003695
199
+ hash: 4282640623209066746
196
200
  required_rubygems_version: !ruby/object:Gem::Requirement
197
201
  none: false
198
202
  requirements:
@@ -201,10 +205,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
201
205
  version: '0'
202
206
  segments:
203
207
  - 0
204
- hash: 2707663932851003695
208
+ hash: 4282640623209066746
205
209
  requirements: []
206
210
  rubyforge_project:
207
- rubygems_version: 1.8.24
211
+ rubygems_version: 1.8.25
208
212
  signing_key:
209
213
  specification_version: 3
210
214
  summary: Test utility for mocking out external web responses
@@ -214,7 +218,10 @@ test_files:
214
218
  - features/step_definitions/test_replay_steps.rb
215
219
  - features/support/env.rb
216
220
  - features/support/mock_web_service.rb
221
+ - features/support/xml_parser.rb
217
222
  - features/test_replay.feature
223
+ - test/message_assertions.rb
224
+ - test/test_endpoint_handlers.rb
218
225
  - test/test_json_message.rb
219
226
  - test/test_message.rb
220
227
  - test/test_pinch_hitter.rb
@@ -1,29 +0,0 @@
1
- module PinchHitter
2
- module Service
3
- class ResponseQueues
4
-
5
- def reset
6
- @responses = {}
7
- end
8
-
9
- def responses
10
- @responses ||= {}
11
- end
12
-
13
- def store(endpoint, body)
14
- endpoint_responses(endpoint) << body.gsub(/\n\s*/, '')
15
- end
16
-
17
- def retrieve(endpoint='/')
18
- endpoint_responses(endpoint).shift
19
- end
20
-
21
- def endpoint_responses(endpoint='/')
22
- endpoint = "/#{endpoint}" unless endpoint =~ /^\//
23
- queue = responses[endpoint] || []
24
- responses[endpoint] = queue
25
- queue
26
- end
27
- end
28
- end
29
- end