fakesmtpd 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/README.md +72 -3
- data/Rakefile +35 -1
- data/fakesmtpd.gemspec +0 -1
- data/lib/fakesmtpd/runner.rb +13 -10
- data/lib/fakesmtpd/server.rb +219 -53
- data/lib/fakesmtpd/version.rb +1 -1
- data/test/fakesmtpd_test.rb +41 -7
- metadata +4 -20
data/.travis.yml
CHANGED
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
|
-
##
|
8
|
-
|
9
|
-
file which may then be used as an executable,
|
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
|
data/fakesmtpd.gemspec
CHANGED
data/lib/fakesmtpd/runner.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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(
|
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
|
data/lib/fakesmtpd/server.rb
CHANGED
@@ -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(
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
client
|
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
|
-
|
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.
|
91
|
-
USAGE =
|
188
|
+
VERSION = '0.2.0'
|
189
|
+
USAGE = <<-EOU.gsub(/^ {6}/, '')
|
190
|
+
Usage: #{File.basename($0)} <smtp-port> <message-dir> [options]
|
92
191
|
|
93
|
-
|
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 = []
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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:
|
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
|
-
|
124
|
-
|
125
|
-
|
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(
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/fakesmtpd/version.rb
CHANGED
data/test/fakesmtpd_test.rb
CHANGED
@@ -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
|
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
|
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 '
|
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
|
-
|
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.
|
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-
|
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:
|
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:
|
92
|
+
hash: 2803386961380935915
|
109
93
|
requirements: []
|
110
94
|
rubyforge_project:
|
111
95
|
rubygems_version: 1.8.23
|