fakesmtpd 0.1.0 → 0.2.0
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/.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
|
+
[](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
|