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.
@@ -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
- def load_config(filename)
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
@@ -1,7 +1,34 @@
1
- require_relative "store/recorder"
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
- attr_reader :identifiers
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 [](column)
19
- Recorder.new(column, self)
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
@@ -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 = Object.new
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
- class << TestMailer
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
@@ -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 = 1
5
- TINY = 1
7
+ MINOR = 2
8
+ TINY = 0
6
9
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
7
10
  end
8
11
  end
@@ -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 "ping_pong"
2
- require_relative "simple_mailing_list"
1
+ require_relative "../helper"
3
2
 
4
- require "minitest/autorun"
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(Newman::Examples::PingPong)
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(Newman::Examples::PingPong)
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 => "tester@test.com",
28
- :to => "test@test.com")
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 => "tester@test.com",
34
- :to => "test+subscribe@test.com")
35
+ mailer.deliver_message(:from => "tester@test.com",
36
+ :to => "test+subscribe@test.com",
37
+ :subject => "subscribe")
35
38
 
36
- server.tick(Newman::Examples::SimpleList)
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(Newman::Examples::SimpleList)
47
+ server.tick
45
48
  mailer.messages.first.subject.must_equal("WIN!")
46
49
 
47
- mailer.deliver_message(:from => "tester@test.com",
48
- :to => "test+unsubscribe@test.com")
50
+ mailer.deliver_message(:from => "tester@test.com",
51
+ :to => "test@test.com",
52
+ :subject => "unsubscribe")
49
53
 
50
- server.tick(Newman::Examples::SimpleList)
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