pinch_hitter 0.2 → 0.3

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 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