fakesmtpd 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,3 +3,7 @@ language: ruby
3
3
  rvm:
4
4
  - 1.9.3
5
5
  - 2.0.0
6
+ notifications:
7
+ email:
8
+ recipients:
9
+ - github+fakesmtpd@modcloth.com
data/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  `fakesmtpd`
2
2
  ===========
3
3
 
4
+ [![Build Status](https://travis-ci.org/modcloth-labs/fakesmtpd.png?branch=master)](https://travis-ci.org/modcloth-labs/fakesmtpd)
5
+
4
6
  A fake SMTP server with a minimal HTTP API.
5
7
  Inspired by [mailtrap](https://github.com/mmower/mailtrap).
6
8
 
7
- ## installation
8
- You may install either via `gem` or by directly downloading the server
9
- file which may then be used as an executable, e.g.:
9
+ ## Installation
10
+ `fakesmtpd` may be installed either as a Ruby gem or by directly
11
+ downloading the server file which may then be used as an executable,
12
+ e.g.:
10
13
 
11
14
  ``` bash
12
15
  gem install fakesmtpd
@@ -19,3 +22,69 @@ curl -o fakesmtpd https://raw.github.com/modcloth-labs/fakesmtpd/master/lib/fake
19
22
  chmod +x fakesmtpd
20
23
  ```
21
24
 
25
+ ## Usage
26
+ `fakesmtpd` is usable either as an executable or from within a Ruby
27
+ process. The following examples are roughly equivalent.
28
+
29
+ ``` bash
30
+ # example command line invocation:
31
+ fakesmtpd 9025 ./fakesmtpd-messages -p ./fakesmtpd.pid -l ./fakesmtpd.log
32
+ ```
33
+
34
+ ``` ruby
35
+ # example in-process Ruby invocation:
36
+ require 'fakesmtpd'
37
+ FakeSMTPd::Server.main(
38
+ %w(9025 ./fakesmtpd-messages -p ./fakesmtpd.pid -l ./fakesmtpd.log)
39
+ )
40
+ ```
41
+
42
+ The `FakeSMTPd::Runner` class is intended to provide a
43
+ formalized way to spawn and manage a separate Ruby process running
44
+ `FakeSMTPd::Server`. This example is also equivalent to those provided
45
+ above:
46
+
47
+ ``` ruby
48
+ require 'fakesmtpd'
49
+
50
+ fakesmtpd = FakeSMTPd::Runner.new(
51
+ dir: File.expand_path('../fakesmtpd-messages', __FILE__),
52
+ port: 9025,
53
+ logfile: File.expand_path('../fakesmtpd.log', __FILE__),
54
+ pidfile: File.expand_path('../fakesmtpd.pid', __FILE__)
55
+ )
56
+ # This spawns another Ruby process via `RbConfig.ruby`
57
+ fakesmtpd.start
58
+
59
+ # ... do other stuff ...
60
+
61
+ # This will kill the previously-spawned process
62
+ fakesmtpd.stop
63
+ ```
64
+
65
+ Once `FakeSMTPd::Server.main` has been invoked via one of the above
66
+ methods, there will be both an SMTP and HTTP server running in a single
67
+ Ruby process. The HTTP port is one more than the SMTP port specified.
68
+
69
+ ### HTTP API Usage
70
+
71
+ The HTTP API provides an index of all sent messages, the ability to
72
+ fetch sent message content, and the ability to clear sent messages
73
+ via the following:
74
+
75
+ #### `GET /`
76
+
77
+ Contains almost nothing but a link to `/messages`.
78
+
79
+ #### `GET /messages`
80
+
81
+ Contains an index of all sent messages, their IDs, their absolute
82
+ filenames, and links to the content for each.
83
+
84
+ #### `GET /messages/[[:digit:]]`
85
+
86
+ Returns the content of the message with the given ID.
87
+
88
+ #### `DELETE /messages`
89
+
90
+ Clears all files out of the sent messages directory.
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
3
  desc 'Run minitest tests in ./test'
4
- task test: :load_minitest do
4
+ task test: [:load_minitest, :clean_artifacts] do
5
5
  Dir.glob("#{File.expand_path('../test', __FILE__)}/*_test.rb").each do |f|
6
6
  require f
7
7
  end
@@ -14,4 +14,38 @@ task :load_minitest do
14
14
  require 'minitest/spec'
15
15
  end
16
16
 
17
+ task :clean_artifacts do
18
+ rm_rf(File.expand_path('../.artifacts', __FILE__))
19
+ end
20
+
21
+ desc 'Send a test email to a local SMTP server (on $PORT)'
22
+ task :send_email do
23
+ smtp_port = Integer(ENV['PORT'])
24
+ require 'net/smtp'
25
+ msg = <<-EOMSG.gsub(/^ {4}/, '')
26
+ From: Fake SMTPd <fakesmtpd@example.org>
27
+ To: Recipient Person <recipient@example.org>
28
+ Subject: Test Sandwich
29
+ Date: Sun, 11 Aug 2013 21:54:13 +0500
30
+ Message-Id: <this.sandwich.is.a.test.#{rand(999..1999)}@example.org>
31
+
32
+ Why have one sandwich when you can have #{rand(2..9)}?
33
+ ____
34
+ .----------' '-.
35
+ / . ' . \\
36
+ / ' . /|
37
+ / . \ /
38
+ / ' . . . || |
39
+ /.___________ ' / //
40
+ |._ '------'| /|
41
+ '.............______.-' /
42
+ jgs |-. | /
43
+ `"""""""""""""-.....-'
44
+
45
+ EOMSG
46
+ Net::SMTP.start('localhost', smtp_port) do |smtp|
47
+ smtp.send_message(msg, 'fakesmtpd@example.org', 'recipient@example.org')
48
+ end
49
+ end
50
+
17
51
  task default: :test
@@ -21,5 +21,4 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_development_dependency 'bundler', '~> 1.3'
23
23
  spec.add_development_dependency 'rake'
24
- spec.add_development_dependency 'pry'
25
24
  end
@@ -5,7 +5,7 @@ require 'logger'
5
5
 
6
6
  class FakeSMTPd::Runner
7
7
  attr_reader :port, :dir, :pidfile, :http_port
8
- attr_reader :startup_sleep, :server_pid, :options
8
+ attr_reader :startup_sleep, :server_pid, :options, :logfile
9
9
 
10
10
  def initialize(options = {})
11
11
  @dir = options.fetch(:dir)
@@ -13,6 +13,7 @@ class FakeSMTPd::Runner
13
13
  @http_port = Integer(options[:http_port] || port + 1)
14
14
  @pidfile = options[:pidfile] || 'fakesmtpd.pid'
15
15
  @startup_sleep = options[:startup_sleep] || 0.5
16
+ @logfile = options[:logfile] || $stderr
16
17
  end
17
18
 
18
19
  def description
@@ -20,13 +21,15 @@ class FakeSMTPd::Runner
20
21
  end
21
22
 
22
23
  def command
23
- [
24
- RbConfig.ruby,
25
- File.expand_path('../server.rb', __FILE__),
26
- port.to_s,
27
- dir,
28
- pidfile,
29
- ].join(' ')
24
+ (
25
+ [
26
+ RbConfig.ruby,
27
+ File.expand_path('../server.rb', __FILE__),
28
+ port.to_s,
29
+ dir,
30
+ '--pidfile', pidfile,
31
+ ] + (logfile == $stderr ? [] : ['--logfile', logfile])
32
+ ).join(' ')
30
33
  end
31
34
 
32
35
  def start
@@ -54,9 +57,9 @@ class FakeSMTPd::Runner
54
57
  end
55
58
 
56
59
  def log
57
- @log ||= Logger.new($stderr).tap do |l|
60
+ @log ||= Logger.new(@logfile).tap do |l|
58
61
  l.formatter = proc do |severity, datetime, _, msg|
59
- "[fakesmtpd] #{severity} #{datetime} - #{msg}\n"
62
+ "[fakesmtpd-runner] #{severity} #{datetime} - #{msg}\n"
60
63
  end
61
64
  end
62
65
  end
@@ -25,13 +25,13 @@
25
25
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
26
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
 
28
+ require 'fileutils'
28
29
  require 'json'
29
30
  require 'logger'
31
+ require 'optparse'
30
32
  require 'socket'
31
33
  require 'thread'
32
34
 
33
- $fakesmtpd_semaphore = Mutex.new
34
-
35
35
  module FakeSMTPd
36
36
  class HTTPServer
37
37
  attr_reader :server, :port, :smtpd, :log
@@ -39,7 +39,7 @@ module FakeSMTPd
39
39
  def initialize(options = {})
40
40
  @port = options.fetch(:port)
41
41
  @smtpd = options.fetch(:smtpd)
42
- @log = Logger.new($stderr).tap do |l|
42
+ @log = Logger.new(options[:logfile]).tap do |l|
43
43
  l.formatter = proc do |severity, datetime, _, msg|
44
44
  "[fakesmtpd-http] #{severity} #{datetime} - #{msg}\n"
45
45
  end
@@ -53,76 +53,203 @@ module FakeSMTPd
53
53
  log.info "PID=#{$$} Thread=#{Thread.current.inspect}"
54
54
  loop do
55
55
  client = httpd.accept
56
- request_line = client.gets
57
- log.info request_line.chomp
58
- if request_line =~ /^GET \/messages /
59
- client.puts 'HTTP/1.1 200 OK'
60
- client.puts 'Content-type: application/json;charset=utf-8'
61
- client.puts
62
- $fakesmtpd_semaphore.synchronize do
63
- client.puts JSON.pretty_generate(
64
- message_files: smtpd.message_files_written
65
- )
66
- end
67
- elsif request_line =~ /^DELETE \/messages /
68
- $fakesmtpd_semaphore.synchronize do
69
- smtpd.message_files_written.clear
70
- end
71
- client.puts 'HTTP/1.1 204 No Content'
72
- client.puts
73
- else
74
- client.puts 'HTTP/1.1 405 Method Not Allowed'
75
- client.puts 'Content-type: text/plain;charset=utf-8'
76
- client.puts
77
- client.puts 'Only "(GET|DELETE) /messages" is supported, eh.'
56
+ begin
57
+ request_line = client.gets
58
+ path = request_line.split[1]
59
+ handle_client(request_line, path, client)
60
+ rescue => e
61
+ handle_500(path, client, e)
78
62
  end
79
- client.close
80
63
  end
81
64
  end
82
65
  end
83
66
 
84
67
  def kill!
85
- @server && @server.kill
68
+ if @server
69
+ log.info "FakeSMTPd HTTP server stopping"
70
+ @server.kill
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def handle_client(request_line, path, client)
77
+ log.info request_line.chomp
78
+ case request_line
79
+ when /^GET \/ /
80
+ handle_get_root(path, client)
81
+ when /^GET \/messages /
82
+ handle_get_messages(path, client)
83
+ when /^GET \/messages\/([[:digit:]]+) /
84
+ handle_get_message(path, client, $1)
85
+ when /^DELETE \/messages /
86
+ handle_clear_messages(path, client)
87
+ else
88
+ handle_404(path, client)
89
+ end
90
+ client.close
91
+ end
92
+
93
+ def handle_get_root(path, client)
94
+ client.puts 'HTTP/1.1 200 OK'
95
+ client.puts 'Content-type: application/json;charset=utf-8'
96
+ client.puts
97
+ client.puts JSON.pretty_generate(
98
+ _links: {
99
+ self: {href: path},
100
+ messages: {href: '/messages'},
101
+ }
102
+ )
103
+ end
104
+
105
+ def handle_get_messages(path, client)
106
+ client.puts 'HTTP/1.1 200 OK'
107
+ client.puts 'Content-type: application/json;charset=utf-8'
108
+ client.puts
109
+ client.puts JSON.pretty_generate(
110
+ _links: {
111
+ self: {href: path}
112
+ },
113
+ _embedded: {
114
+ messages: smtpd.messages.to_hash.map do |message_id, filename|
115
+ {
116
+ _links: {
117
+ self: {href: "/messages/#{message_id}"}
118
+ },
119
+ message_id: message_id,
120
+ filename: filename
121
+ }
122
+ end
123
+ }
124
+ )
125
+ end
126
+
127
+ def handle_get_message(path, client, message_id)
128
+ message_file = smtpd.messages[message_id]
129
+ if message_file
130
+ client.puts 'HTTP/1.1 200 OK'
131
+ client.puts 'Content-type: application/json;charset=utf-8'
132
+ client.puts
133
+ message = JSON.parse(File.read(message_file))
134
+ client.puts JSON.pretty_generate(
135
+ message.merge(
136
+ _links: {
137
+ self: {href: path}
138
+ },
139
+ filename: message_file
140
+ )
141
+ )
142
+ else
143
+ client.puts 'HTTP/1.1 404 Not Found'
144
+ client.puts 'Content-type: application/json;charset=utf-8'
145
+ client.puts
146
+ client.puts JSON.pretty_generate(
147
+ _links: {
148
+ self: {href: path}
149
+ },
150
+ error: "Message #{message_id.inspect} not found"
151
+ )
152
+ end
153
+ end
154
+
155
+ def handle_clear_messages(path, client)
156
+ smtpd.messages.clear
157
+ client.puts 'HTTP/1.1 204 No Content'
158
+ client.puts
159
+ end
160
+
161
+ def handle_404(path, client)
162
+ client.puts 'HTTP/1.1 404 Not Found'
163
+ client.puts 'Content-type: application/json;charset=utf-8'
164
+ client.puts
165
+ client.puts JSON.pretty_generate(
166
+ _links: {
167
+ self: {href: path}
168
+ },
169
+ error: 'Nothing is here'
170
+ )
171
+ end
172
+
173
+ def handle_500(path, client, e)
174
+ client.puts 'HTTP/1.1 500 Internal Server Error'
175
+ client.puts 'Content-type: application/json;charset=utf-8'
176
+ client.puts
177
+ client.puts JSON.pretty_generate(
178
+ _links: {
179
+ self: {href: path}
180
+ },
181
+ error: "#{e.class.name} #{e.message}",
182
+ backtrace: e.backtrace
183
+ )
86
184
  end
87
185
  end
88
186
 
89
187
  class Server
90
- VERSION = '0.1.0'
91
- USAGE = "Usage: #{File.basename($0)} <port> <message-dir> [pidfile]"
188
+ VERSION = '0.2.0'
189
+ USAGE = <<-EOU.gsub(/^ {6}/, '')
190
+ Usage: #{File.basename($0)} <smtp-port> <message-dir> [options]
92
191
 
93
- attr_reader :port, :message_dir, :log, :pidfile, :message_files_written
192
+ The `<smtp-port>` argument will be incremented by 1 for the HTTP API port.
193
+ The `<message-dir>` is where each SMTP transaction will be written as a
194
+ JSON file containing the "smtp client id" (timestamp from beginning of SMTP
195
+ transaction), the sender, recipients, and combined headers and body as
196
+ an array of strings.
197
+
198
+ EOU
199
+
200
+ attr_reader :port, :message_dir, :log, :logfile, :pidfile
201
+ attr_reader :messages
94
202
 
95
203
  class << self
96
- def main(argv = [].freeze)
97
- if argv.include?('-h') || argv.include?('--help')
98
- puts USAGE
99
- exit 0
100
- end
101
- if argv.include?('--version')
102
- puts FakeSMTPd::Server::VERSION
103
- exit 0
104
- end
105
- unless argv.length > 1
204
+ def main(argv = [])
205
+ options = {
206
+ pidfile: nil,
207
+ logfile: $stderr,
208
+ }
209
+
210
+ OptionParser.new do |opts|
211
+ opts.banner = USAGE
212
+ opts.on('--version', 'Show version and exit') do |*|
213
+ puts "fakesmtpd #{FakeSMTPd::Server::VERSION}"
214
+ exit 0
215
+ end
216
+ opts.on('-p PIDFILE', '--pidfile PIDFILE',
217
+ 'Optional file where process PID will be written') do |pidfile|
218
+ options[:pidfile] = pidfile
219
+ end
220
+ opts.on('-l LOGFILE', '--logfile LOGFILE',
221
+ 'Optional file where all log messages will be written ' <<
222
+ '(default $stderr)') do |logfile|
223
+ options[:logfile] = logfile
224
+ end
225
+ end.parse!(argv)
226
+
227
+ unless argv.length == 2
106
228
  abort USAGE
107
229
  end
230
+
108
231
  @smtpd = FakeSMTPd::Server.new(
109
232
  port: Integer(argv.fetch(0)),
110
233
  dir: argv.fetch(1),
111
- pidfile: argv[2]
234
+ pidfile: options[:pidfile],
235
+ logfile: options[:logfile],
112
236
  )
113
237
  @httpd = FakeSMTPd::HTTPServer.new(
114
238
  port: Integer(argv.fetch(0)) + 1,
115
239
  smtpd: @smtpd,
240
+ logfile: options[:logfile],
116
241
  )
117
242
 
118
- $stderr.puts '--- Starting up ---'
119
243
  @httpd.start
120
244
  @smtpd.start
121
245
  loop { sleep 1 }
122
246
  rescue Exception => e
123
- $stderr.puts '--- Shutting down ---'
124
- @httpd && @httpd.kill!
125
- @smtpd && @smtpd.kill!
247
+ if @httpd
248
+ @httpd.kill!
249
+ end
250
+ if @smtpd
251
+ @smtpd.kill!
252
+ end
126
253
  unless e.is_a?(Interrupt)
127
254
  raise e
128
255
  end
@@ -133,12 +260,12 @@ module FakeSMTPd
133
260
  @port = options.fetch(:port)
134
261
  @message_dir = options.fetch(:dir)
135
262
  @pidfile = options[:pidfile] || 'fakesmtpd.pid'
136
- @log = Logger.new($stderr).tap do |l|
263
+ @log = Logger.new(options[:logfile]).tap do |l|
137
264
  l.formatter = proc do |severity, datetime, _, msg|
138
265
  "[fakesmtpd-smtp] #{severity} #{datetime} - #{msg}\n"
139
266
  end
140
267
  end
141
- @message_files_written = []
268
+ @messages = MessageStore.new(@message_dir)
142
269
  end
143
270
 
144
271
  def start
@@ -160,7 +287,10 @@ module FakeSMTPd
160
287
  end
161
288
 
162
289
  def kill!
163
- @server && @server.kill
290
+ if @server
291
+ log.info "FakeSMTPd SMTP server stopping"
292
+ @server.kill
293
+ end
164
294
  end
165
295
 
166
296
  def serve(client)
@@ -226,20 +356,56 @@ module FakeSMTPd
226
356
  end
227
357
 
228
358
  def record(client, from, recipients, body)
229
- outfile = File.join(message_dir, "fakesmtpd-client-#{client.client_id}.json")
359
+ messages.store(
360
+ client.client_id, from, recipients, body
361
+ )
362
+ end
363
+ end
364
+
365
+ class MessageStore
366
+ attr_reader :message_dir
367
+
368
+ def initialize(message_dir)
369
+ @message_dir = message_dir
370
+ end
371
+
372
+ def store(message_id, from, recipients, body)
373
+ outfile = File.join(message_dir, "fakesmtpd-client-#{message_id}.json")
230
374
  File.open(outfile, 'w') do |f|
231
375
  f.write JSON.pretty_generate(
232
- client_id: client.client_id,
376
+ message_id: message_id,
233
377
  from: from,
234
378
  recipients: recipients,
235
379
  body: body,
236
380
  )
237
381
  end
238
- $fakesmtpd_semaphore.synchronize do
239
- message_files_written << outfile
240
- end
241
382
  outfile
242
383
  end
384
+
385
+ def to_hash
386
+ message_files.each_with_object({}) do |filename, h|
387
+ message_id = File.basename(filename, '.json').gsub(/[^0-9]+/, '')
388
+ h[message_id] = File.expand_path(filename)
389
+ end
390
+ end
391
+
392
+ def [](message_id)
393
+ message_file = "#{message_dir}/fakesmtpd-client-#{message_id}.json"
394
+ if File.exists?(message_file)
395
+ return message_file
396
+ end
397
+ nil
398
+ end
399
+
400
+ def clear
401
+ FileUtils.rm_f(message_files)
402
+ end
403
+
404
+ private
405
+
406
+ def message_files
407
+ Dir.glob("#{message_dir}/fakesmtpd-client-*.json")
408
+ end
243
409
  end
244
410
  end
245
411
 
@@ -1,3 +1,3 @@
1
1
  module FakeSMTPd
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -8,11 +8,17 @@ require 'fakesmtpd'
8
8
  describe 'fakesmtpd server' do
9
9
  RUNNER = FakeSMTPd::Runner.new(
10
10
  dir: File.expand_path('../../.artifacts', __FILE__),
11
- port: rand(9100..9199)
11
+ port: rand(9100..9199),
12
+ logfile: File.expand_path('../../.artifacts/fakesmtpd.log', __FILE__),
13
+ pidfile: File.expand_path('../../.artifacts/fakesmtpd.pid', __FILE__),
12
14
  )
13
15
  RUNNER.start
14
16
 
15
- at_exit { RUNNER.stop }
17
+ at_exit do
18
+ RUNNER.stop
19
+ end
20
+
21
+ after(:each) { clear_messages }
16
22
 
17
23
  def randint
18
24
  @randint ||= rand(999..1999)
@@ -36,21 +42,49 @@ describe 'fakesmtpd server' do
36
42
 
37
43
  def send_message
38
44
  Net::SMTP.start('localhost', RUNNER.port) do |smtp|
39
- smtp.send_message msg, 'fakesmtpd@example.org', 'fruitcake@example.org'
45
+ smtp.send_message(msg, 'fakesmtpd@example.org', 'fruitcake@example.org')
40
46
  end
41
47
  end
42
48
 
49
+ def get_messages
50
+ uri = URI("http://localhost:#{RUNNER.http_port}/messages")
51
+ JSON.parse(Net::HTTP.get_response(uri).body)
52
+ end
53
+
54
+ def clear_messages
55
+ Net::HTTP.start('localhost', RUNNER.http_port) do |http|
56
+ http.request(Net::HTTP::Delete.new('/messages'))
57
+ end
58
+ end
59
+
60
+ def get_message(message_id)
61
+ uri = URI("http://localhost:#{RUNNER.http_port}/messages/#{message_id}")
62
+ JSON.parse(Net::HTTP.get_response(uri).body)
63
+ end
64
+
43
65
  it 'accepts messages via SMTP' do
44
66
  send_message
45
67
  end
46
68
 
47
- it 'reports messages sent via HTTP' do
69
+ it 'supports clearing sent messages via HTTP' do
70
+ clear_messages
71
+ end
72
+
73
+ it 'supports getting messages sent via HTTP' do
48
74
  send_message
75
+ response = get_messages
49
76
 
50
- uri = URI("http://localhost:#{RUNNER.http_port}/messages")
51
- response = JSON.parse(Net::HTTP.get_response(uri).body)
52
- message_file = response.fetch('message_files').last
77
+ message_file = response.fetch('_embedded').fetch('messages').last.fetch('filename')
53
78
  message_body = JSON.parse(File.read(message_file)).fetch('body')
54
79
  message_body.must_include("Subject: #{subject_header}")
55
80
  end
81
+
82
+ it 'supports getting individual messages sent via HTTP' do
83
+ send_message
84
+ response = get_messages
85
+
86
+ message_id = response.fetch('_embedded').fetch('messages').last.fetch('message_id')
87
+ message_body = get_message(message_id).fetch('body')
88
+ message_body.must_include("Subject: #{subject_header}")
89
+ end
56
90
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fakesmtpd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
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-08-11 00:00:00.000000000 Z
12
+ date: 2013-08-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -43,22 +43,6 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
- - !ruby/object:Gem::Dependency
47
- name: pry
48
- requirement: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ! '>='
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
46
  description: A fake SMTP server with a minimal HTTP API
63
47
  email:
64
48
  - d.buch@modcloth.com
@@ -96,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
80
  version: '0'
97
81
  segments:
98
82
  - 0
99
- hash: 55468274672838870
83
+ hash: 2803386961380935915
100
84
  required_rubygems_version: !ruby/object:Gem::Requirement
101
85
  none: false
102
86
  requirements:
@@ -105,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
89
  version: '0'
106
90
  segments:
107
91
  - 0
108
- hash: 55468274672838870
92
+ hash: 2803386961380935915
109
93
  requirements: []
110
94
  rubyforge_project:
111
95
  rubygems_version: 1.8.23