newman 0.1.1 → 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/CHANGELOG.md +23 -0
- data/Gemfile +3 -0
- data/README.md +126 -7
- data/examples/config/dreamhost.rb.example +5 -1
- data/examples/config/gmail.rb.example +4 -0
- data/examples/live_test.rb +4 -7
- data/examples/simple_mailing_list.rb +2 -2
- data/lib/newman.rb +97 -1
- data/lib/newman/application.rb +238 -1
- data/lib/newman/controller.rb +93 -8
- data/lib/newman/email_logger.rb +54 -0
- data/lib/newman/filters.rb +123 -0
- data/lib/newman/mailer.rb +98 -32
- data/lib/newman/mailing_list.rb +83 -0
- data/lib/newman/recorder.rb +132 -0
- data/lib/newman/request_logger.rb +41 -0
- data/lib/newman/response_logger.rb +41 -0
- data/lib/newman/server.rb +197 -23
- data/lib/newman/settings.rb +72 -4
- data/lib/newman/store.rb +68 -4
- data/lib/newman/test_mailer.rb +79 -6
- data/lib/newman/version.rb +5 -2
- data/test/helper.rb +18 -0
- data/{examples → test/integration}/acid_tests.rb +30 -20
- data/test/integration/skip_response_test.rb +22 -0
- data/test/integration/subject_filter_test.rb +25 -0
- data/test/settings.rb +4 -0
- data/test/suite.rb +6 -0
- metadata +74 -9
- data/lib/newman/commands.rb +0 -72
- data/lib/newman/store/recorder.rb +0 -63
data/lib/newman/settings.rb
CHANGED
@@ -1,9 +1,60 @@
|
|
1
|
+
# `Newman::Settings` provides the base functionality that is used by Newman's
|
2
|
+
# configuration files. It is currently a thin wrapper on top of Ruby's
|
3
|
+
# `OpenStruct` construct, but will later add some domain specific validations
|
4
|
+
# and transformations for the various configuration options it supports.
|
5
|
+
#
|
6
|
+
# Unless you need to customize the way that Newman's configuration system
|
7
|
+
# works or make changes to your settings objects at runtime, you probably don't
|
8
|
+
# need to worry about how this object is implemented. Both
|
9
|
+
# `Newman::Server.simple` and `Newman::Server.test_mode` create a
|
10
|
+
# `Newman::Settings` object for you automatically, and you will typically be
|
11
|
+
# tweaking a sample settings file rather than crafting one from scratch.
|
12
|
+
#
|
13
|
+
# `Newman::Settings`is part of Newman's **internal API**, but
|
14
|
+
# the setting file format and various settings that Newman depends
|
15
|
+
# on should be considered part of the **external API**.
|
16
|
+
|
1
17
|
module Newman
|
2
18
|
class Settings
|
19
|
+
|
20
|
+
# ---
|
21
|
+
|
22
|
+
# The `Newman::Settings.from_file` method is used to create
|
23
|
+
# a new `Newman::Settings` object and populate it with the data
|
24
|
+
# contained in a settings file, i.e.
|
25
|
+
#
|
26
|
+
# settings = Newman::Settings.from_file('config/environment.rb')
|
27
|
+
#
|
28
|
+
# This method is purely syntactic sugar, and is functionally equivalent to
|
29
|
+
# the following code:
|
30
|
+
#
|
31
|
+
# settings = Newman::Settings.new
|
32
|
+
# settings.load_config('config/environment.rb')
|
33
|
+
#
|
34
|
+
# Because there currently is little advantage to explicitly instantiating a
|
35
|
+
# blank `Newman::Settings` object, this method is the preferred way of doing
|
36
|
+
# things.
|
37
|
+
#
|
3
38
|
def self.from_file(filename)
|
4
39
|
new.tap { |o| o.load_config(filename) }
|
5
40
|
end
|
6
|
-
|
41
|
+
|
42
|
+
# ---
|
43
|
+
|
44
|
+
# A `Newman::Settings` object is a blank slate upon creation. It simply
|
45
|
+
# assigns an empty `OpenStruct` object for each type of settings data it
|
46
|
+
# supports.
|
47
|
+
#
|
48
|
+
# In most situations, you will not instantiate a `Newman::Settings` object
|
49
|
+
# directly but instead will make use of a configuration file and the
|
50
|
+
# `Newman::Settings.from_file` method. Newman provides sample configuration
|
51
|
+
# files in its `examples/` and `test/` directories, and applications should do the
|
52
|
+
# same. This will help users discover what fields can be set.
|
53
|
+
#
|
54
|
+
# We are aware of the fact that the current configuration system is way too
|
55
|
+
# flexible and a breeding ground for subtle bugs. This will be fixed in a
|
56
|
+
# future version of Newman.
|
57
|
+
|
7
58
|
def initialize
|
8
59
|
self.imap = OpenStruct.new
|
9
60
|
self.smtp = OpenStruct.new
|
@@ -11,11 +62,28 @@ module Newman
|
|
11
62
|
self.application = OpenStruct.new
|
12
63
|
end
|
13
64
|
|
14
|
-
|
15
|
-
eval(File.read(filename), binding)
|
16
|
-
end
|
65
|
+
# ---
|
17
66
|
|
67
|
+
# The `imap` and `smtp` fields are used by `Newman::Mailer`,
|
68
|
+
# the `service` field is used throughout Newman (particularly in
|
69
|
+
# `Newman::Server`), and the `application` field is reserved for
|
70
|
+
# application-specific configurations.
|
18
71
|
|
19
72
|
attr_accessor :imap, :smtp, :service, :application
|
73
|
+
|
74
|
+
|
75
|
+
# ---
|
76
|
+
|
77
|
+
# `Newman::Settings#load_config` is used for evaluating
|
78
|
+
# the contents of a file within the context of a `Newman::Settings`
|
79
|
+
# instance.
|
80
|
+
#
|
81
|
+
# In practice, this method is typically called by
|
82
|
+
# `Newman::Settings#from_file`, but can also be used to apply
|
83
|
+
# multiple settings files to a single `Newman::Settings` object
|
84
|
+
|
85
|
+
def load_config(filename)
|
86
|
+
eval(File.read(filename), binding)
|
87
|
+
end
|
20
88
|
end
|
21
89
|
end
|
data/lib/newman/store.rb
CHANGED
@@ -1,7 +1,34 @@
|
|
1
|
-
|
1
|
+
# `Newman::Store` is a minimal persistence layer for storing non-relational
|
2
|
+
# data. It is meant to make the task of building small applications with simple
|
3
|
+
# data storage needs easier.
|
4
|
+
#
|
5
|
+
# For an example of how `Newman::Store` can be used in your applications, you
|
6
|
+
# can take a look at how `Newman::MailingList` is implemented. A similar
|
7
|
+
# approach could be used to develop arbitrary persistent models.
|
8
|
+
#
|
9
|
+
# `Newman::Store` is part of Newman's **external interface**.
|
2
10
|
|
3
11
|
module Newman
|
4
12
|
class Store
|
13
|
+
|
14
|
+
# ---
|
15
|
+
|
16
|
+
# To initialize a `Newman::Store` object, a `filename` string must
|
17
|
+
# be provided, i.e.
|
18
|
+
#
|
19
|
+
# store = Newman::Store.new("simple.store")
|
20
|
+
#
|
21
|
+
# This filename will be used to initialize a `PStore` object after first
|
22
|
+
# running `FileUtils.mkdir_p` to create any directories within the path to
|
23
|
+
# the filename if they do not already exist. Once that `PStore` object is
|
24
|
+
# created, two root keys will be mapped to empty Hash objects if they
|
25
|
+
# are not set already: `:indentifers` and `:columns`.
|
26
|
+
#
|
27
|
+
# While it's okay to treat the `PStore` object as an implementation detail,
|
28
|
+
# we will treat our interactions with it as part of Newman's **external
|
29
|
+
# interface**, so that we are more conservative about making backwards
|
30
|
+
# incompatible changes to the databases created by `Newman::Store`.
|
31
|
+
|
5
32
|
def initialize(filename)
|
6
33
|
FileUtils.mkdir_p(File.dirname(filename))
|
7
34
|
|
@@ -13,22 +40,59 @@ module Newman
|
|
13
40
|
end
|
14
41
|
end
|
15
42
|
|
16
|
-
|
43
|
+
# ---
|
44
|
+
|
45
|
+
# `Newman::Store#[]` is syntactic sugar for initializing a
|
46
|
+
# `Newman::Recorder` object, and is meant to be used for
|
47
|
+
# accessing and manipulating column data by `column_key`, i.e.
|
48
|
+
#
|
49
|
+
# store[:subscriptions].create("gregory.t.brown@gmail.com")
|
50
|
+
#
|
51
|
+
# This method is functionally equivalent to the following code:
|
52
|
+
#
|
53
|
+
# recorder = Newman::Recorder.new(:subscriptions, store)
|
54
|
+
# recorder.create("gregory.t.brown@gmail.com")
|
55
|
+
#
|
56
|
+
# For aesthetic reasons and for forward compatibility, it is
|
57
|
+
# preferable to use `Newman::Store#[]` rather than instantiating
|
58
|
+
# a `Newman::Recorder` object directly.
|
17
59
|
|
18
|
-
def [](
|
19
|
-
Recorder.new(
|
60
|
+
def [](column_key)
|
61
|
+
Recorder.new(column_key, self)
|
20
62
|
end
|
21
63
|
|
64
|
+
# ---
|
65
|
+
|
66
|
+
# `Newman::Store#read` initiates a read only transaction and then yields
|
67
|
+
# the underlying `PStore` object stored in the `data` field.
|
68
|
+
|
22
69
|
def read
|
23
70
|
data.transaction(:read_only) { yield(data) }
|
24
71
|
end
|
25
72
|
|
73
|
+
# ---
|
74
|
+
|
75
|
+
# `Newman::Store#read` initiates a read/write transaction and then yields
|
76
|
+
# the underlying `PStore` object stored in the `data` field.
|
77
|
+
|
26
78
|
def write
|
27
79
|
data.transaction { yield(data) }
|
28
80
|
end
|
29
81
|
|
82
|
+
# ---
|
83
|
+
|
84
|
+
# **NOTE: Methods below this point in the file are implementation
|
85
|
+
# details, and should not be depended upon**
|
86
|
+
|
30
87
|
private
|
31
88
|
|
89
|
+
# ---
|
90
|
+
|
91
|
+
# The `data` accessor is kept private because a `Newman::Store` object is
|
92
|
+
# meant to wrap a single `PStore` object once created, and because we want
|
93
|
+
# to force every interaction with a `Newman::Store` to be transactional in
|
94
|
+
# nature.
|
95
|
+
|
32
96
|
attr_accessor :data
|
33
97
|
end
|
34
98
|
end
|
data/lib/newman/test_mailer.rb
CHANGED
@@ -1,14 +1,74 @@
|
|
1
|
+
# `Newman::TestMailer` is a drop-in replacement for `Newman::Mailer` meant for
|
2
|
+
# use in automated testing. It is a thin wrapper on top of the built in testing
|
3
|
+
# functionality provided by the mail gem.
|
4
|
+
#
|
5
|
+
# `Newman::TestMailer` may be a useful tool for Newman application developers,
|
6
|
+
# at some point but is a bit tricky to work with due to the fact that it
|
7
|
+
# relies on global state. This is a known issue and will hopefully be solved in
|
8
|
+
# a future version of Newman.
|
9
|
+
#
|
10
|
+
# `Newman::TestMailer` is part of Newman's **internal interface**, but may
|
11
|
+
# become part of the **external interface** if we can make it less brittle.
|
12
|
+
# Patches are welcome!
|
13
|
+
|
1
14
|
module Newman
|
2
|
-
TestMailer
|
15
|
+
class TestMailer
|
16
|
+
# ---
|
17
|
+
|
18
|
+
# To initialize a `Newman::TestMailer` object, a settings object must be
|
19
|
+
# provided, i.e.
|
20
|
+
#
|
21
|
+
# settings = Newman::Settings.from_file('config/environment.rb')
|
22
|
+
# mailer = Newman::TestMailer.new(settings)
|
23
|
+
#
|
24
|
+
# However, there are handful of caveats worth knowing about this
|
25
|
+
# constructing an instance of this particular object.
|
26
|
+
#
|
27
|
+
# 1) Most unit tests won't need a `Newman::TestMailer` object present, and most
|
28
|
+
# integration tests can make use of `Newman::Server.test_mode`, preventing
|
29
|
+
# the need to ever explicitly instantiate a `Newman::TestMailer` object.
|
30
|
+
#
|
31
|
+
# 2) Because there isn't an obvious way to work with test objects in the
|
32
|
+
# underlying mail gem without relying on global state,
|
33
|
+
# `Newman::TestMailer` actually implements the singleton pattern and
|
34
|
+
# returns references to a single instance rather than creating new
|
35
|
+
# instances. The constructor interface is simply preserved so that it
|
36
|
+
# can be a drop-in replacement for a `Newman::Mailer` object.
|
37
|
+
#
|
38
|
+
# 3) The settings object is not actually used, and is only part of the
|
39
|
+
# signature for API compatibility reasons.
|
40
|
+
#
|
41
|
+
# With these caveats in mind, be sure to think long and hard about whether
|
42
|
+
# you actually need to explicitly build instances of this object before
|
43
|
+
# doing so :)
|
44
|
+
|
45
|
+
class << self
|
46
|
+
def new(settings)
|
47
|
+
return self.instance if instance
|
48
|
+
|
49
|
+
Mail.defaults do
|
50
|
+
retriever_method :test
|
51
|
+
delivery_method :test
|
52
|
+
end
|
3
53
|
|
4
|
-
|
5
|
-
def configure(settings)
|
6
|
-
Mail.defaults do
|
7
|
-
retriever_method :test
|
8
|
-
delivery_method :test
|
54
|
+
self.instance = allocate
|
9
55
|
end
|
56
|
+
|
57
|
+
attr_accessor :instance
|
10
58
|
end
|
11
59
|
|
60
|
+
# ---
|
61
|
+
|
62
|
+
# `Newman::TestMailer#messages` is used to retrieve all messages currently in the inbox
|
63
|
+
# and then delete them from the underlying `Mail::TestMailer` object so that
|
64
|
+
# the inbox gets cleared. This method returns an array of
|
65
|
+
# `Mail::Message` objects if any messages were found, and returns
|
66
|
+
# an empty array otherwise.
|
67
|
+
#
|
68
|
+
# Keep in mind that because only a single `Newman::TestMailer` ever gets
|
69
|
+
# instantiated no matter how many times you call `Newman::TestMailer.new`,
|
70
|
+
# you only get one test inbox per process.
|
71
|
+
|
12
72
|
def messages
|
13
73
|
msgs = Marshal.load(Marshal.dump(Mail::TestMailer.deliveries))
|
14
74
|
Mail::TestMailer.deliveries.clear
|
@@ -16,10 +76,23 @@ module Newman
|
|
16
76
|
msgs
|
17
77
|
end
|
18
78
|
|
79
|
+
# ---
|
80
|
+
|
81
|
+
# `Newman::TestMailer#new_message` is used to construct a new `Mail::Message` object,
|
82
|
+
# with the delivery settings set to test mode.
|
83
|
+
# This method passes all its arguments on to `Mail.new`, so be sure
|
84
|
+
# to refer to the [mail gem's documentation](http://github.com/mikel/mail)
|
85
|
+
# for details.
|
86
|
+
|
19
87
|
def new_message(*a, &b)
|
20
88
|
Mail.new(*a, &b)
|
21
89
|
end
|
22
90
|
|
91
|
+
# ---
|
92
|
+
|
93
|
+
# `Newman::TestMailer#deliver_message` method is used to construct and immediately deliver a
|
94
|
+
# message with the delivery settings set to test mode.
|
95
|
+
|
23
96
|
def deliver_message(*a, &b)
|
24
97
|
new_message(*a, &b).deliver
|
25
98
|
end
|
data/lib/newman/version.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
# Newman::Version is used to determine which version of Newman is currently
|
2
|
+
# running and is part of Newman's **external api**.
|
3
|
+
|
1
4
|
module Newman
|
2
5
|
module Version
|
3
6
|
MAJOR = 0
|
4
|
-
MINOR =
|
5
|
-
TINY =
|
7
|
+
MINOR = 2
|
8
|
+
TINY = 0
|
6
9
|
STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
|
7
10
|
end
|
8
11
|
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
gem "minitest"
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "purdytest"
|
5
|
+
require_relative "../lib/newman"
|
6
|
+
|
7
|
+
module Newman
|
8
|
+
TEST_DIR = File.dirname(__FILE__)
|
9
|
+
|
10
|
+
def self.new_test_server(app)
|
11
|
+
server = Newman::Server.test_mode(TEST_DIR + "/settings.rb")
|
12
|
+
server.apps << app
|
13
|
+
server.settings.application.simplelist_db = TEST_DIR + "/test.store"
|
14
|
+
server.settings.service.templates_dir = TEST_DIR + "/../examples/views"
|
15
|
+
|
16
|
+
server
|
17
|
+
end
|
18
|
+
end
|
@@ -1,39 +1,42 @@
|
|
1
|
-
require_relative "
|
2
|
-
require_relative "simple_mailing_list"
|
1
|
+
require_relative "../helper"
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
server = Newman::Server
|
7
|
-
server.test_mode("config/test.rb")
|
8
|
-
|
9
|
-
mailer = server.mailer
|
3
|
+
require_relative "../../examples/ping_pong"
|
4
|
+
require_relative "../../examples/simple_mailing_list"
|
10
5
|
|
11
6
|
describe "Ping Pong" do
|
7
|
+
let(:server) { Newman.new_test_server(Newman::Examples::PingPong) }
|
8
|
+
let(:mailer) { server.mailer }
|
9
|
+
|
12
10
|
it "responds to an email sent to test+ping@test.com" do
|
13
11
|
mailer.deliver_message(:to => "test+ping@test.com")
|
14
|
-
server.tick
|
12
|
+
server.tick
|
15
13
|
mailer.messages.first.subject.must_equal("pong")
|
16
14
|
end
|
17
15
|
|
18
16
|
it "responds to an email sent to test+bad@test.com" do
|
19
17
|
mailer.deliver_message(:to => "test+bad@test.com")
|
20
|
-
server.tick
|
18
|
+
server.tick
|
21
19
|
mailer.messages.first.subject.must_equal("unknown command")
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
25
23
|
describe "SimpleList" do
|
24
|
+
let(:server) { Newman.new_test_server(Newman::Examples::SimpleList) }
|
25
|
+
let(:mailer) { server.mailer }
|
26
|
+
|
26
27
|
it "emulates a simple mailing list" do
|
27
|
-
mailer.deliver_message(:from
|
28
|
-
:to
|
28
|
+
mailer.deliver_message(:from => "tester@test.com",
|
29
|
+
:to => "test@test.com")
|
30
|
+
|
31
|
+
server.tick
|
29
32
|
|
30
|
-
server.tick(Newman::Examples::SimpleList)
|
31
33
|
mailer.messages.first.subject.must_equal("You are not subscribed")
|
32
34
|
|
33
|
-
mailer.deliver_message(:from
|
34
|
-
:to
|
35
|
+
mailer.deliver_message(:from => "tester@test.com",
|
36
|
+
:to => "test+subscribe@test.com",
|
37
|
+
:subject => "subscribe")
|
35
38
|
|
36
|
-
server.tick
|
39
|
+
server.tick
|
37
40
|
mailer.messages.first.subject.must_equal("SUBSCRIBED!")
|
38
41
|
|
39
42
|
|
@@ -41,13 +44,14 @@ describe "SimpleList" do
|
|
41
44
|
:to => "test@test.com",
|
42
45
|
:subject => "WIN!")
|
43
46
|
|
44
|
-
server.tick
|
47
|
+
server.tick
|
45
48
|
mailer.messages.first.subject.must_equal("WIN!")
|
46
49
|
|
47
|
-
mailer.deliver_message(:from
|
48
|
-
:to
|
50
|
+
mailer.deliver_message(:from => "tester@test.com",
|
51
|
+
:to => "test@test.com",
|
52
|
+
:subject => "unsubscribe")
|
49
53
|
|
50
|
-
server.tick
|
54
|
+
server.tick
|
51
55
|
mailer.messages.first.subject.must_equal("UNSUBSCRIBED!")
|
52
56
|
end
|
53
57
|
|
@@ -57,3 +61,9 @@ describe "SimpleList" do
|
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
64
|
+
|
65
|
+
|
66
|
+
# move this when test suite set up
|
67
|
+
|
68
|
+
|
69
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative "../helper"
|
2
|
+
|
3
|
+
describe "Server handling request without responding" do
|
4
|
+
|
5
|
+
let(:noop) do
|
6
|
+
Newman::Application.new do
|
7
|
+
default do
|
8
|
+
skip_response
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:server) { Newman.new_test_server(noop) }
|
14
|
+
let(:mailer) { server.mailer }
|
15
|
+
|
16
|
+
it "should not deliver message" do
|
17
|
+
mailer.deliver_message(:from => 'tester@test.com',
|
18
|
+
:to => 'test+noop@test.com')
|
19
|
+
server.tick
|
20
|
+
assert_empty mailer.messages
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "../helper"
|
2
|
+
|
3
|
+
describe "subject filter" do
|
4
|
+
let(:app) do
|
5
|
+
Newman::Application.new do
|
6
|
+
subject :match, "hello" do
|
7
|
+
respond(:subject => "HELLOOOOOOOO!")
|
8
|
+
end
|
9
|
+
|
10
|
+
default do
|
11
|
+
respond(:subject => "No subject, no greeting")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:server) { Newman.new_test_server(app) }
|
17
|
+
let(:mailer) { server.mailer }
|
18
|
+
|
19
|
+
it "should automatically not match emails without subjects" do
|
20
|
+
mailer.deliver_message(:to => "test@test.com")
|
21
|
+
server.tick
|
22
|
+
|
23
|
+
mailer.messages.first.subject.must_equal("No subject, no greeting")
|
24
|
+
end
|
25
|
+
end
|