mockingbird 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Hayes Davis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,102 @@
1
+ Mockingbird
2
+ ===========
3
+ Mockingbird emulates the Twitter Streaming API using a simple script-like
4
+ configuration that makes it easy to test code that connects to the Streaming
5
+ API. Mockingbird can be used to simulate bad data, unexpected status codes,
6
+ hard disconnects, etc.
7
+
8
+ Mockingbird uses eventmachine to run as an actual streaming http server so
9
+ it's a drop-in replacement for code that reads from the streaming api.
10
+ Simply change the host and port your code is connecting to from Twitter to
11
+ a mockingbird instance.
12
+
13
+ Since mockingbird is designed for testing, it includes a simple
14
+ Mockingbird#setup and Mockingbird#teardown interface that makes it easy to
15
+ configure and spawn a server for testing purposes during unit tests.
16
+
17
+ Configuration Quickstart
18
+ ------------------------
19
+ Mockingbird uses a simple script-like configuration API for telling a
20
+ mockingbird server what you want it to do. Here's a simple example:
21
+
22
+ Mockingbird.setup(:port=>8080) do
23
+ send '{"foo":"bar"}'
24
+ wait 1
25
+ 5.times do
26
+ send '{"foo2":"bar2"}'
27
+ end
28
+ pipe "some/file.txt", :wait=>1
29
+ close
30
+ end
31
+
32
+ Here's what this does in plain english:
33
+
34
+ * Tells the server to listen on port 8080 and do the stuff in the block on
35
+ each connection.
36
+ * On a connection, send '{"foo":"bar"}' down to the client
37
+ * Wait 1 second
38
+ * Then send '{"foo2":"bar2"}' down to the client 5 times
39
+ * Then send each line from some/file.txt to the clien, waiting 1 second in
40
+ between sends
41
+ * Close the connection
42
+
43
+ Mockingbird assigns each conection an incrementing id. This means you can
44
+ specify behavior over multiple connections with different connections doing
45
+ different things. This is handy for testing reconnection code:
46
+
47
+ Mockingbird.setup(:port=>8080) do
48
+ on_connection(1) do
49
+ disconnect!
50
+ end
51
+
52
+ on_connection(2..5) do
53
+ wait(0.5)
54
+ close
55
+ end
56
+
57
+ on_connection('*') do
58
+ 100.times do
59
+ send '{"foo":"bar"}'
60
+ end
61
+ close
62
+ end
63
+ end
64
+
65
+ Again, in plain english:
66
+
67
+ * On the first connection, we do a hard disconnect (just drop the connection)
68
+ * On connections 2-5, wait a half second, then close the connection nicely
69
+ * On all subsequent connections ("*"), send down 100 foo bars and close
70
+
71
+ See the docs on Mockingbird::Script for all the available configuration options.
72
+ Also, see the examples directory for more usage.
73
+
74
+ Using in Tests
75
+ --------------
76
+ The basic pattern for using Mockingbird in your unit tests is to simply call
77
+ Mockingbird#setup and Mockingbird#teardown at appropriate times. This will
78
+ setup a mockingbird server in a separate process and then kill it when you're
79
+ done. Make sure to *always* call Mockingbird#teardown. This is easy in test/unit
80
+ if you're actually calling these methods in setup and teardown. If you need to
81
+ setup and teardown a server in a test method, do the following:
82
+
83
+ def test_something
84
+ Mockingbird.setup(:port=>NNNN) do
85
+ # config here
86
+ end
87
+ # do tests
88
+ ensure
89
+ Mockingbird.teardown
90
+ end
91
+
92
+ Limitations
93
+ -----------
94
+ * SSL is not supported.
95
+ * Since connection ids are incrementing with each connection you won't be able
96
+ able to easily target specific connections if you have multiple clients
97
+ connecting at once to the mockingbird server. It's generally recommended that
98
+ you have a single client connecting to mockingbird serially during a single
99
+ test run. Doing otherwise would probably be confusing anyway.
100
+ * The server does not even pay attention to your actual request, it will just
101
+ always respond with the defined configuration script regardless of what you
102
+ send on connection.
@@ -0,0 +1,35 @@
1
+ How to run examples
2
+ ===================
3
+ It's easiest to do this if you have curl. From the root directory of this
4
+ project run the following
5
+
6
+ $ ruby examples/EXAMPLE_FILE.rb
7
+ Waiting for Mockingbird to start...
8
+ Mockingbird is mocking you on 0.0.0.0:8080 (pid=50990)
9
+ $ curl -v http://localhost:8080
10
+
11
+ Depending on the example file and your version of curl, you'll see something
12
+ similar to this:
13
+ * About to connect() to 0.0.0.0 port 8080 (#0)
14
+ * Trying 0.0.0.0... connected
15
+ * Connected to 0.0.0.0 (0.0.0.0) port 8080 (#0)
16
+ > GET / HTTP/1.1
17
+ > User-Agent: curl/7.19.6 (i386-apple-darwin9.7.0) libcurl/7.19.6 zlib/1.2.3
18
+ > Host: 0.0.0.0:8080
19
+ > Accept: */*
20
+ >
21
+ < HTTP/1.1 200 OK
22
+ < Transfer-Encoding: chunked
23
+ < Content-Type: application/json
24
+ < Server: Mockingbird
25
+ <
26
+ * Connection #0 to host 0.0.0.0 left intact
27
+ * Closing connection #0
28
+
29
+ When you're done you need to kill the mockingbird server which is running in
30
+ its own process. You can grep it like below or use the pid printed when you
31
+ ran the example file.
32
+
33
+ $ ps | grep mockingbird
34
+ 50990 ttys006 0:00.33 mockingbird:0.0.0.0:8080
35
+ $ kill -9 50990
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ require 'mockingbird'
5
+
6
+ Mockingbird.setup(:port=>8080) do
7
+ send '{"foo":"bar"}'
8
+ wait 0.5
9
+ 5.times do |n|
10
+ send %Q({"foo":"bar#{n}"})
11
+ end
12
+ pipe "#{File.dirname(__FILE__)}/overview_output.txt", :wait=>1
13
+ close
14
+ end
@@ -0,0 +1,5 @@
1
+ {"line":1}
2
+ {"line":2}
3
+ {"line":3}
4
+ {"line":4}
5
+ {"line":5}
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ require 'mockingbird'
5
+
6
+ # Note: you'll need to run curl several times to see the full effect of this one
7
+
8
+ Mockingbird.setup(:port=>8080) do
9
+ on_connection(1) do
10
+ disconnect!
11
+ end
12
+
13
+ on_connection(2..5) do
14
+ wait(0.5)
15
+ close
16
+ end
17
+
18
+ on_connection('*') do
19
+ 100.times do
20
+ send '{"foo":"bar"}'
21
+ end
22
+ close
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ require 'mockingbird'
5
+
6
+ Mockingbird.setup(:port=>8080) do
7
+ status 404, "Not Found"
8
+ headers "X-Hi-There"=>"Howdy"
9
+ close
10
+ end
@@ -0,0 +1,61 @@
1
+ # External dependencies
2
+ require 'rubygems'
3
+ require 'eventmachine'
4
+
5
+ # Mockingbird code
6
+ require 'mockingbird/version'
7
+ require 'mockingbird/commands'
8
+ require 'mockingbird/connection_script'
9
+ require 'mockingbird/script'
10
+ require 'mockingbird/script_runner'
11
+ require 'mockingbird/server'
12
+
13
+ module Mockingbird
14
+
15
+ class << self
16
+
17
+ # Convenience method for starting a mockingbird server during a test.
18
+ # This will be most users' primary interface to mockingbird. The mockingbird
19
+ # server will be forked as a separate process. Ensure that your test code
20
+ # always calls teardown at some point after setup, or the separate process
21
+ # will not be terminated.
22
+ #
23
+ # Options are
24
+ # :host - The host to listen on. 0.0.0.0 by default
25
+ # :port - The port to listen on. 4879 by default
26
+ #
27
+ # The block is a Mockingbird configuration (see README).
28
+ def setup(opts={},&block)
29
+ Server.configure(&block)
30
+ @pid = fork do
31
+ opts = {:host=>'0.0.0.0',:port=>4879}.merge(opts)
32
+ $0 = "mockingbird:#{opts[:host]}:#{opts[:port]}"
33
+ Server.start!(opts)
34
+ end
35
+ Process.detach(@pid)
36
+ puts "Waiting for Mockingbird to start..."
37
+ sleep(1) # Necessary to make sure the forked proc is up and running
38
+ @pid
39
+ end
40
+
41
+ # Terminates the mockingbird server created by a call to setup. Make sure
42
+ # to always pair this call with setup to ensure that mockingbird server
43
+ # processes don't linger. If you're using test/unit, the recommended
44
+ # pattern is to actually call setup and teardown here during the setup and
45
+ # teardown phase of your unit tests. Otherwise, use the following pattern:
46
+ #
47
+ # def test_something
48
+ # Mockingbird.setup(:port=>NNNN) do
49
+ # # config here
50
+ # end
51
+ # # do tests
52
+ # ensure
53
+ # Mockingbird.teardown
54
+ # end
55
+ def teardown
56
+ Process.kill('KILL',@pid)
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,135 @@
1
+ module Mockingbird
2
+ module Commands
3
+ class Command
4
+
5
+ attr_accessor :next_command, :callback
6
+
7
+ def initialize(&block)
8
+ self.callback = block
9
+ end
10
+
11
+ def run(conn)
12
+ callback.call(conn) if callback
13
+ advance(conn)
14
+ end
15
+
16
+ def advance(conn)
17
+ next_command.run(conn) if next_command
18
+ end
19
+
20
+ end
21
+
22
+ class Send < Command
23
+
24
+ def initialize(data=nil,&block)
25
+ @data = data || block
26
+ end
27
+
28
+ def run(conn)
29
+ to_send = data
30
+ conn.send_chunk(to_send)
31
+ advance(conn)
32
+ end
33
+
34
+ private
35
+ def data
36
+ if @data.respond_to?(:call)
37
+ @data.call
38
+ else
39
+ @data
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ class Disconnect < Command
46
+
47
+ def run(conn)
48
+ conn.close_connection
49
+ advance(conn)
50
+ end
51
+
52
+ end
53
+
54
+ class Wait < Command
55
+
56
+ def initialize(time=nil,&block)
57
+ @time = time || block
58
+ end
59
+
60
+ def run(conn)
61
+ EM.add_timer(wait_time) do
62
+ advance(conn)
63
+ end
64
+ end
65
+
66
+ private
67
+ def wait_time
68
+ if @time.respond_to?(:call)
69
+ @time.call
70
+ else
71
+ @time
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ class Close < Command
78
+
79
+ def run(conn)
80
+ conn.send_terminal_chunk
81
+ EM.add_timer(0.1) do
82
+ conn.close_connection
83
+ end
84
+ advance(conn)
85
+ end
86
+
87
+ end
88
+
89
+ class Quit < Command
90
+ def run(conn)
91
+ EM.add_timer(0.5) do
92
+ EM.stop
93
+ end
94
+ end
95
+ end
96
+
97
+ class Pipe < Command
98
+
99
+ def initialize(string_or_io,delay=nil)
100
+ if string_or_io.kind_of?(String)
101
+ @io = File.open(string_or_io,'r')
102
+ else
103
+ @io = string_or_io
104
+ end
105
+ @delay = delay
106
+ end
107
+
108
+ def run(conn)
109
+ unless @io.eof?
110
+ chunk = @io.readline.chomp
111
+ conn.send_chunk(chunk)
112
+ if delay
113
+ EM.add_timer(delay) { run(conn) }
114
+ else
115
+ EM.schedule { run(conn) }
116
+ end
117
+ else
118
+ # Reset for future calls
119
+ @io.rewind
120
+ advance(conn)
121
+ end
122
+ end
123
+
124
+ private
125
+ def delay
126
+ if @delay.respond_to?(:call)
127
+ @delay.call
128
+ else
129
+ @delay
130
+ end
131
+ end
132
+
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,19 @@
1
+ module Mockingbird
2
+
3
+ class ConnectionScript
4
+
5
+ attr_accessor :status, :headers, :body
6
+
7
+ def initialize
8
+ self.body = Commands::Command.new
9
+ @last_command = body
10
+ end
11
+
12
+ def add_command(command=nil)
13
+ @last_command.next_command = command
14
+ @last_command = command
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,100 @@
1
+ module Mockingbird
2
+ class Script
3
+
4
+ def initialize(&block)
5
+ @connections = []
6
+ @default_connection = ConnectionScript.new
7
+ instance_eval(&block)
8
+ end
9
+
10
+ def for_connection(id)
11
+ match = @connections.find do |selector, *|
12
+ case selector
13
+ when Range then selector.include?(id)
14
+ when Numeric then selector == id
15
+ when Proc then selector.call(id)
16
+ end
17
+ end
18
+ match ? match.last : @default_connection
19
+ end
20
+
21
+ # Configuration API
22
+
23
+ # Specifies behavior to run for specific connections based on the id number
24
+ # assigned to that connection. Connection ids are 1-based.
25
+ #
26
+ # The selector can be any of the following:
27
+ # Number - Run the code on that connection id
28
+ # Range - Run the code for a connection id in that range
29
+ # Proc - Call the proc with the id and run if it matches
30
+ # '*' - Run this code for any connection that doesn't match others
31
+ def on_connection(selector=nil,&block)
32
+ if selector.nil? || selector == '*'
33
+ instance_eval(&block)
34
+ else
35
+ @current_connection = ConnectionScript.new
36
+ instance_eval(&block)
37
+ @connections << [selector,@current_connection]
38
+ @current_connection = nil
39
+ end
40
+ end
41
+
42
+ # Send an HTTP status code to the client. e.g. a 403 or 404
43
+ def status(code, message="")
44
+ current_connection.status = [code, message]
45
+ end
46
+
47
+ # Send the hash of headers to the client.
48
+ def headers(hash)
49
+ current_connection.headers = hash
50
+ end
51
+
52
+ # Send some text down to the client. If a block is specified that block
53
+ # will be called on each connection to determine what text to send. This
54
+ # permits sending randomized data.
55
+ def send(data=nil,&block)
56
+ add_command(Commands::Send.new(data,&block))
57
+ end
58
+
59
+ # Wait a certain number of seconds. Fractional seconds are allowed. If a
60
+ # block is specified, it will be called on each attempt. This permits things
61
+ # like adding randomized waits.
62
+ def wait(time=nil,&block)
63
+ add_command(Commands::Wait.new(time,&block))
64
+ end
65
+
66
+ # Perform a hard disconnect by just closing the connection
67
+ def disconnect!
68
+ add_command(Commands::Disconnect.new)
69
+ end
70
+
71
+ # Do a clean close
72
+ def close
73
+ add_command(Commands::Close.new)
74
+ end
75
+
76
+ # Exit the server entirely
77
+ def quit
78
+ add_command(Commands::Quit.new)
79
+ end
80
+
81
+ # Send the lines from string_or_io down one at a time. The :wait option
82
+ # can be used to specify a configurable delay between lines.
83
+ def pipe(string_or_io,opts={})
84
+ add_command(Commands::Pipe.new(string_or_io,opts[:wait]))
85
+ end
86
+
87
+ # Not really part of the public API but users could use this to
88
+ # implement their own fancy command
89
+ def add_command(command=nil,&block)
90
+ command = Commands::Command.new(&block) if block_given?
91
+ current_connection.add_command(command)
92
+ end
93
+
94
+ private
95
+ def current_connection
96
+ @current_connection || @default_connection
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ module Mockingbird
2
+
3
+ class ScriptRunner
4
+
5
+ attr_accessor :conn, :script
6
+
7
+ def initialize(conn,script)
8
+ self.conn = conn
9
+ self.script = script
10
+ end
11
+
12
+ def run
13
+ send_status
14
+ send_headers
15
+ send_body
16
+ end
17
+
18
+ def send_status
19
+ code, message = (script.status || [200,"OK"])
20
+ conn.send_status(code,message)
21
+ end
22
+
23
+ def send_headers
24
+ headers = {
25
+ "Transfer-Encoding"=>"chunked",
26
+ "Content-Type"=>"application/json",
27
+ "Server"=>"Mockingbird"
28
+ }.merge(script.headers||{})
29
+ headers.each do |name, value|
30
+ conn.send_header(name, value)
31
+ end
32
+ end
33
+
34
+ def send_body
35
+ conn.start_body
36
+ script.body.run(conn)
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,83 @@
1
+ module Mockingbird
2
+
3
+ # A very simple eventmachine-based streaming server. Before you use a server
4
+ # it must be configured with configuration block:
5
+ #
6
+ # Mockingbird::Server.configure do
7
+ # # config goes here, see README for scripting
8
+ # end
9
+ #
10
+ # Once configured, a server may be started using
11
+ #
12
+ # start!(:host=>'0.0.0.0',:port=>NNN)
13
+ #
14
+ # If you're using Mockingbird directly from test (test/unit, etc), it's
15
+ # recommended that you use the simpler convenience interface defined on
16
+ # the Mockingbird module.
17
+ module Server
18
+
19
+ class << self
20
+
21
+ def start!(opts={})
22
+ opts = {:host=>'0.0.0.0',:port=>8080}.merge(opts)
23
+ host = opts[:host]
24
+ port = opts[:port]
25
+ EventMachine::run do
26
+ puts "Mockingbird is mocking you on #{host}:#{port} (pid=#{$$})"
27
+ EventMachine::start_server host, port, self
28
+ end
29
+ end
30
+
31
+ def configure(&block)
32
+ @script = Script.new(&block)
33
+ end
34
+
35
+ def script
36
+ @script
37
+ end
38
+
39
+ def new_connection_id
40
+ @connection_id ||= 0
41
+ @connection_id += 1
42
+ end
43
+
44
+ end
45
+
46
+ def receive_data(data)
47
+ conn_id = new_connection_id
48
+ runner = ScriptRunner.new(self,script.for_connection(conn_id))
49
+ runner.run
50
+ end
51
+
52
+ def new_connection_id
53
+ Mockingbird::Server.new_connection_id
54
+ end
55
+
56
+ def script
57
+ Mockingbird::Server.script
58
+ end
59
+
60
+ def send_status(code=200,text="OK")
61
+ send_data "HTTP/1.1 #{code} #{text}\r\n"
62
+ end
63
+
64
+ def send_header(name,value)
65
+ send_data "#{name}: #{value}\r\n"
66
+ end
67
+
68
+ def start_body
69
+ send_data "\r\n"
70
+ end
71
+
72
+ def send_chunk(chunk)
73
+ res = %Q(#{chunk}\r\n)
74
+ send_data "#{res.length.to_s(16)}\r\n"
75
+ send_data "#{res}\r\n"
76
+ end
77
+
78
+ def send_terminal_chunk
79
+ send_data "0\r\n\r\n"
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ module Mockingbird
2
+
3
+ VERSION = Version = "0.1.0"
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mockingbird
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Hayes Davis
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-31 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: eventmachine
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 47
30
+ segments:
31
+ - 0
32
+ - 12
33
+ - 0
34
+ version: 0.12.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: " Mockingbird emulates the Twitter Streaming API using a simple script-like \n configuration that makes it easy to test code that connects to the Streaming \n API. Mockingbird can be used to simulate bad data, unexpected status codes, \n hard disconnects, etc.\n \n Mockingbird uses eventmachine to run as an actual streaming http server so\n it's a drop-in replacement for code that reads from the streaming api. \n Simply change the host and port your code is connecting to from Twitter to \n a running Mockingbird.\n"
38
+ email: hayes@appozite.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - LICENSE
45
+ - README.md
46
+ files:
47
+ - README.md
48
+ - lib/mockingbird/commands.rb
49
+ - lib/mockingbird/connection_script.rb
50
+ - lib/mockingbird/script.rb
51
+ - lib/mockingbird/script_runner.rb
52
+ - lib/mockingbird/server.rb
53
+ - lib/mockingbird/version.rb
54
+ - lib/mockingbird.rb
55
+ - examples/examples.md
56
+ - examples/overview.rb
57
+ - examples/overview_output.txt
58
+ - examples/reconnects.rb
59
+ - examples/status_and_headers.rb
60
+ - LICENSE
61
+ has_rdoc: true
62
+ homepage: http://github.com/hayesdavis/mockingbird
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.3.7
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Mockingbird is a mock server for testing with the Twitter Streaming API.
95
+ test_files: []
96
+