newman 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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