sortah 0.5.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/spec/bin_spec.rb ADDED
@@ -0,0 +1,54 @@
1
+ def run_with(arg, email = "")
2
+ #uses the sortah defn in spec/fixtures/rc
3
+ cmd =<<-CMD
4
+ bundle exec bin/sortah #{arg} --rc "spec/fixtures/rc" <<-EMAIL
5
+ #{email}
6
+ EMAIL
7
+ CMD
8
+ result = `#{cmd}`
9
+
10
+ { result: result, status: $?.to_i }
11
+ end
12
+
13
+
14
+ describe "the sortah executable" do
15
+ before :all do
16
+ Mail.defaults do
17
+ delivery_method :test
18
+ end
19
+
20
+ @email = Mail.new do
21
+ to 'testa@example.com'
22
+ from 'chuck@nope.com'
23
+ subject "Taximerdizin'"
24
+ body <<-TXT
25
+ OJAI VALLEY TAXIDERMY
26
+
27
+ BET YOU THOUGHT THIS EMAIL WAS REAL
28
+
29
+ NOPE. CHUCK TESTA
30
+ TXT
31
+ end
32
+ end
33
+
34
+ before :each do
35
+ Sortah::Parser.clear!
36
+ end
37
+
38
+ context "when executing in dry-run mode, " do
39
+ it "should have a dry-run mode" do
40
+ cmd = run_with("--dry-run")
41
+ cmd[:result].should =~ /Dry-run mode/
42
+ cmd[:status].should == 0
43
+ end
44
+
45
+ it "should not write any files when in dry-run mode" do
46
+ run_with('--dry-run', @email.to_s)
47
+ Dir['/tmp/mail/*'].size.should == 0
48
+ end
49
+
50
+ it "should print to STDOUT the location it intends to write the file" do
51
+ run_with('--dry-run', @email.to_s)[:result].should =~ %r|writing email to: /tmp/\.mail/foo/|
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sortah::Destination do
4
+
5
+ context "when creating a destination" do
6
+ it "should be able to show me it's destination path" do
7
+ dest = Sortah::Destination.new(:foo, "bar")
8
+ dest.path.should == "bar"
9
+ end
10
+ end
11
+
12
+ context "when comparing a destination with another object, " do
13
+ it "should be equal to itself" do
14
+ dest = Sortah::Destination.new(:foo, "bar")
15
+ (dest == dest).should be_true
16
+ end
17
+
18
+ it "should not be equal to a destination which has a different path" do
19
+ dest = Sortah::Destination.new(:foo, "bar")
20
+ dest2 = Sortah::Destination.new(:bar, "baz")
21
+ (dest == dest2).should_not be_true
22
+ end
23
+
24
+ it "should not be equal to a destination which has the same path, but different name" do
25
+ dest = Sortah::Destination.new(:foo, "baz")
26
+ dest2 = Sortah::Destination.new(:bar, "baz")
27
+ (dest == dest2).should_not be_true
28
+ end
29
+
30
+ it "should be equal to an equivalent destination" do
31
+ dest = Sortah::Destination.new(:foo, "baz")
32
+ dest2 = Sortah::Destination.new(:foo, "baz")
33
+ (dest == dest2).should be_true
34
+ end
35
+
36
+ it "should be equal to a string which is identical to the absolute path it describes" do
37
+ dest = Sortah::Destination.new(:foo, "baz")
38
+ (dest == "baz").should be_true
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ describe Sortah::Email do
3
+ before :each do
4
+ @email = Sortah::Email.wrap(Mail.new)
5
+ end
6
+
7
+ it "should proxy the Mail class" do
8
+ (Mail.new.methods - Object.methods).each do |method|
9
+ @email.should respond_to method
10
+ end
11
+ end
12
+ end
13
+
data/spec/fixtures/rc ADDED
@@ -0,0 +1,8 @@
1
+ sortah do
2
+ maildir '/tmp/.mail/'
3
+ destination :foo, 'foo/'
4
+
5
+ router do
6
+ send_to :foo
7
+ end
8
+ end
@@ -0,0 +1,270 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sortah::Parser do
4
+ context "when parsing language components, " do
5
+ before :each do
6
+ Sortah::Parser.clear!
7
+ end
8
+
9
+ context "when parsing destinations, " do
10
+ it "should provide an environment for definiton" do
11
+ expect {
12
+ sortah do
13
+ end
14
+ }.should_not raise_error
15
+ sortah.should_not be_nil
16
+ end
17
+
18
+ it "should parse defined 'simple' destinations" do
19
+ expect {
20
+ sortah do
21
+ destination :place, "somewhere/"
22
+ end
23
+ }.should_not raise_error
24
+ sortah.destinations[:place].should == "somewhere/"
25
+ end
26
+
27
+ it "should parse defined 'absolute path' destinations" do
28
+ expect {
29
+ sortah do
30
+ destination :place, :abs => "/home/user/.mail/.somewhere.else/"
31
+ end
32
+ }.should_not raise_error
33
+ sortah.destinations[:place].should == "/home/user/.mail/.somewhere.else/"
34
+ end
35
+
36
+ it "should parse defined 'alias' destinations in a dereferenced way" do
37
+ expect {
38
+ sortah do
39
+ destination :place, "somewhere/"
40
+ destination :other_place, :place
41
+ end
42
+ }.should_not raise_error
43
+ sortah.destinations[:other_place].should == "somewhere/"
44
+ end
45
+
46
+ it "should throw a parse error when you try to redefine a destination" do
47
+ expect {
48
+ sortah do
49
+ destination :same_dest, "dest/"
50
+ destination :same_dest, "dest/"
51
+ end
52
+ }.should raise_error Sortah::ParseErrorException
53
+ end
54
+
55
+ end
56
+
57
+ context "when parsing lenses," do
58
+
59
+ it "should parse a lens definition" do
60
+ expect {
61
+ sortah do
62
+ lens :test_value do
63
+ 1
64
+ end
65
+ end
66
+ }.should_not raise_error
67
+ end
68
+
69
+ it "should parse a lens definition that depends on another lens" do
70
+ expect {
71
+ sortah do
72
+ lens :dep do
73
+ 1
74
+ end
75
+
76
+ lens :test_value, lenses: [:dep] do
77
+ 2
78
+ end
79
+ end
80
+ }.should_not raise_error
81
+ end
82
+
83
+ it "should throw a parse error if you try to define the same lens (by name) twice" do
84
+ expect {
85
+ sortah do
86
+ lens :same_name do
87
+ end
88
+ lens :same_name do
89
+ end
90
+ end
91
+ }.should raise_error Sortah::ParseErrorException
92
+ end
93
+
94
+ it "should throw a parse error if you try to reference a lens which does not exist" do
95
+ expect {
96
+ sortah do
97
+ lens :other_name, :lenses => [:non_existant] do
98
+ end
99
+ end
100
+ }.should raise_error Sortah::ParseErrorException
101
+
102
+ end
103
+
104
+ it "should not throw a parse error if you try to forward-reference a lens" do
105
+ expect {
106
+ sortah do
107
+ lens :forward_reference, :lenses => [:ahead] do
108
+ end
109
+ lens :ahead do
110
+ end
111
+ end
112
+ }.should_not raise_error Sortah::ParseErrorException
113
+ end
114
+
115
+ it "should not throw a parse error if you have a cyclic-lens dependency" do
116
+ expect {
117
+ sortah do
118
+ lens :circle_one, :lenses => [:circle_two] do
119
+ end
120
+ lens :circle_two, :lenses => [:circle_one] do
121
+ end
122
+ end
123
+ }.should_not raise_error Sortah::ParseErrorException
124
+ end
125
+
126
+ end
127
+
128
+ context "when parsing routers, " do
129
+
130
+ it "should parse a router definition" do
131
+ expect {
132
+ sortah do
133
+ router :test_router do
134
+ end
135
+ end
136
+ }.should_not raise_error
137
+
138
+ end
139
+
140
+ it "should parse a root-router definition" do
141
+ expect {
142
+ sortah do
143
+ router do
144
+ end
145
+ end
146
+ }.should_not raise_error
147
+ end
148
+
149
+ it "should parse a router with lenses" do
150
+ expect {
151
+ sortah do
152
+ lens :foo do
153
+ end
154
+
155
+ router :test_router, :lenses => [:foo] do
156
+ end
157
+ end
158
+ }.should_not raise_error
159
+ end
160
+
161
+ it "should parse a root-router with lenses" do
162
+ expect {
163
+ sortah do
164
+ lens :foo do
165
+ end
166
+
167
+ router :lenses => [:foo] do
168
+ end
169
+ end
170
+ }.should_not raise_error
171
+ end
172
+
173
+ it "should parse a router with a forward reference to a lens" do
174
+ expect {
175
+ sortah do
176
+ router :foo_router, :lenses => [:foo] do
177
+ end
178
+
179
+ lens :foo do
180
+ end
181
+ end
182
+ }.should_not raise_error
183
+ end
184
+
185
+ it "should parse a root-router with a forward reference to a lens" do
186
+ expect {
187
+ sortah do
188
+ router :lenses => [:foo] do
189
+ end
190
+
191
+ lens :foo do
192
+ end
193
+ end
194
+ }.should_not raise_error
195
+ end
196
+
197
+ end
198
+
199
+ context "when dealing in general with sortah, " do
200
+
201
+ it "should maintain one state across multiple sortah blocks" do
202
+ expect {
203
+ sortah do
204
+ destination :place, "somewhere/"
205
+ end
206
+ }.should_not raise_error
207
+
208
+ expect {
209
+ sortah do
210
+ destination :new_place, :place
211
+ end
212
+ }.should_not raise_error
213
+
214
+ sortah.destinations[:place].should == "somewhere/"
215
+ sortah.destinations[:new_place].should == "somewhere/"
216
+ end
217
+
218
+ it "should allow for configuration" do
219
+ sortah do
220
+ maildir "/home/user/.mail" #mail directory, maildir format
221
+ end
222
+ sortah.maildir.should == "/home/user/.mail"
223
+ end
224
+
225
+ it "should use the last defined maildir" do
226
+ sortah do
227
+ maildir "/home/user/.mail/work"
228
+ maildir "/home/user/.mail/personal"
229
+ end
230
+ sortah.maildir.should == "/home/user/.mail/personal"
231
+ end
232
+
233
+ end
234
+
235
+ #acceptance criteria
236
+ it "should parse an example sortah file, which contains all of the language elements" do
237
+ expect {
238
+ sortah do
239
+ destination :place, "somewhere"
240
+ destination :devnull, :abs => "/dev/null"
241
+ destination :bitbucket, :devnull
242
+
243
+ lens :random_value do
244
+ rand
245
+ end
246
+
247
+ lens :random_spam_value, :lenses => [:random_value] do
248
+ email.random_value * 10
249
+ end
250
+
251
+ lens :also_depends_on_random_value, :lenses => [:random_value] do
252
+ email.random_value * 100
253
+ end
254
+
255
+ router :root, :lenses => [:random_spam_value] do
256
+ if email.random_spam_value > 0.5
257
+ send_to :other_router
258
+ else
259
+ send_to :bitbucket
260
+ end
261
+ end
262
+
263
+ router :other_router, :lenses => [:also_depends_on_random_value] do
264
+ send_to :place
265
+ end
266
+ end
267
+ }.should_not raise_error
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,310 @@
1
+ require 'spec_helper'
2
+ require 'mail'
3
+
4
+ #TODO: Move to spec_helper?
5
+ def basic_sortah_definition
6
+ sortah do
7
+ maildir "/home/jfredett/.mail"
8
+ destination :foo, "foo/"
9
+ destination :bar, "bar/"
10
+ router do
11
+ if email.from.any? { |sender| sender =~ /chuck/ }
12
+ send_to :foo
13
+ else
14
+ send_to :bar
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ describe Sortah do
21
+ context "when sorting an email" do
22
+ before :all do
23
+ Mail.defaults do
24
+ delivery_method :test
25
+ end
26
+
27
+ @email = Mail.new do
28
+ to 'testa@example.com'
29
+ from 'chuck@nope.com'
30
+ subject "Taximerdizin'"
31
+ body <<-TXT
32
+ OJAI VALLEY TAXIDERMY
33
+
34
+ BET YOU THOUGHT THIS EMAIL WAS REAL
35
+
36
+ NOPE. CHUCK TESTA
37
+ TXT
38
+ end
39
+
40
+ @reply_email = Mail.new do
41
+ to 'chuck@nope.com'
42
+ from 'jgf@somewhere.com'
43
+ subject "Re: Taximerdizin'"
44
+ reply_to 'chuck@nope.com'
45
+ body <<-TXT
46
+ > OJAI VALLEY TAXIDERMY
47
+ >
48
+ > BET YOU THOUGHT THIS EMAIL WAS REAL
49
+ >
50
+ > NOPE. CHUCK TESTA
51
+
52
+ Do you taxidermize pets?
53
+ TXT
54
+ end
55
+ end
56
+
57
+ before :each do
58
+ Sortah::Parser.clear!
59
+ end
60
+
61
+ it "should provide a way to sort a single email" do
62
+ sortah.should respond_to :sort
63
+ end
64
+
65
+ it "should throw a sematic error when calling #sort with no root router is provided" do
66
+ sortah do
67
+ destination :foo, "foo/"
68
+ router :not_root do
69
+ send_to :foo
70
+ end
71
+ end
72
+ expect { sortah.sort(@email) }.should raise_error Sortah::NoRootRouterException
73
+ end
74
+
75
+ describe "#sort" do
76
+ it "should return an object which responds to #destination" do
77
+ basic_sortah_definition
78
+ sortah.sort(@email).should respond_to :destination
79
+ end
80
+
81
+ it "should return an object which responds to #metadata" do
82
+ basic_sortah_definition
83
+ sortah.sort(@email).should respond_to :metadata
84
+ end
85
+
86
+ it "should sort emails based on the sortah definitions" do
87
+ basic_sortah_definition
88
+ sortah.sort(@email).destination.should == "foo/"
89
+ sortah.sort(@reply_email).destination.should == "bar/"
90
+ end
91
+
92
+ it "should defer to a second router if it is sent to one" do
93
+ sortah do
94
+ destination :foo, "foo/"
95
+ destination :bar, "bar/"
96
+
97
+ router do
98
+ if email.from.any? { |sender| sender =~ /chuck/ }
99
+ send_to :foo
100
+ else
101
+ send_to :secondary_router
102
+ end
103
+ end
104
+
105
+ router :secondary_router do
106
+ send_to :bar
107
+ end
108
+ end
109
+
110
+ sortah.sort(@email).destination.should == "foo/"
111
+ sortah.sort(@reply_email).destination.should == "bar/"
112
+ end
113
+
114
+ it "should run dependent lenses for the root router" do
115
+ sortah do
116
+ destination :foo, "foo/"
117
+
118
+ lens :senders do
119
+ email.from.map { |s| s.split('@').first }
120
+ end
121
+
122
+ router :root, :lenses => [:senders] do
123
+ send_to :foo
124
+ end
125
+ end
126
+ sortah.sort(@email).metadata(:senders).should == ["chuck"]
127
+ end
128
+
129
+ it "should run dependent lenses for the non-root router, but only if the router gets called" do
130
+ sortah do
131
+ destination :foo, "foo/"
132
+
133
+ lens :senders do
134
+ email.from.map { |s| s.split('@').first }
135
+ end
136
+
137
+ lens :never_called do
138
+ "This should never be called, since the router that depends on it never gets called"
139
+ end
140
+
141
+ router :root, :lenses => [:senders] do
142
+ send_to :foo
143
+ end
144
+
145
+ router :bar, :lenses => [:never_called] do
146
+ "doesn't matter what I put in here"
147
+ end
148
+ end
149
+ sortah.sort(@email).metadata(:never_called).should be_nil
150
+ end
151
+
152
+ it "should never run a lens that isn't a dependency" do
153
+ sortah do
154
+ destination :foo, "foo/"
155
+
156
+ lens :senders do
157
+ email.from.map { |s| s.split('@').first }
158
+ end
159
+
160
+ lens :never_called do
161
+ "This should never be called, since the router that depends on it never gets called"
162
+ end
163
+
164
+ router :root, :lenses => [:senders] do
165
+ send_to :foo
166
+ end
167
+ end
168
+ sortah.sort(@email).metadata(:never_called).should be_nil
169
+ end
170
+
171
+ it "should run provide access to the metadata generated by a lens through the email object" do
172
+ sortah do
173
+ destination :foo, "foo/"
174
+ destination :bar, "bar/"
175
+
176
+ lens :senders do
177
+ email.from.map { |s| s.split('@').first }
178
+ end
179
+
180
+ lens :never_called do
181
+ "This should never be called, since the router that depends on it never gets called"
182
+ end
183
+
184
+ router :root, :lenses => [:senders] do
185
+ if email.senders.include? "chuck"
186
+ send_to :foo
187
+ else
188
+ send_to :bar
189
+ end
190
+ end
191
+ end
192
+ sortah.sort(@email).destination.should == "foo/"
193
+ sortah.sort(@reply_email).destination.should == "bar/"
194
+ end
195
+
196
+ it "should run subdependencies of lenses" do
197
+ sortah do
198
+ destination :foo, "foo/"
199
+ destination :bar, "bar/"
200
+
201
+ lens :senders, :lenses => [:sub_dep] do
202
+ email.from.map { |s| s.split('@').first }
203
+ end
204
+
205
+ lens :sub_dep do
206
+ "Sub Dep Ran"
207
+ end
208
+
209
+ router :root, :lenses => [:senders] do
210
+ if email.senders.include? "chuck"
211
+ send_to :foo
212
+ else
213
+ send_to :bar
214
+ end
215
+ end
216
+ end
217
+ sortah.sort(@email).metadata(:sub_dep).should == "Sub Dep Ran"
218
+ end
219
+
220
+ it "should not run the same lens twice" do
221
+ $count = 0 #evil, pure evil
222
+ sortah do
223
+ destination :bar, "bar/"
224
+
225
+ lens :inc do
226
+ $count += 1
227
+ end
228
+
229
+ router :root, :lenses => [:inc] do
230
+ send_to :baz
231
+ end
232
+
233
+ router :baz, :lenses => [:inc] do
234
+ send_to :bar
235
+ end
236
+ end
237
+
238
+ sortah.sort(@email).metadata(:inc).should == 1
239
+
240
+ $count = nil #undefine $count
241
+ end
242
+
243
+ it "should not set any metadata for a :pass_through lens" do
244
+ sortah do
245
+ destination :foo, "foo/"
246
+
247
+ lens :passthrough, :pass_through => true do
248
+ "some external service call"
249
+ end
250
+
251
+ router :root, :lenses => [:passthrough] do
252
+ send_to :baz
253
+ end
254
+
255
+ router :baz, :lenses => [:passthrough] do
256
+ send_to :foo
257
+ end
258
+ end
259
+ sortah.sort(@email).metadata(:passthrough).should be_nil
260
+ end
261
+
262
+ it "should not run a pass_through lens more than once" do
263
+ $count = 0
264
+ sortah do
265
+ destination :foo, "foo/"
266
+
267
+ lens :passthrough, :pass_through => true do
268
+ $count += 1
269
+ end
270
+
271
+ router :root, :lenses => [:passthrough] do
272
+ send_to :baz
273
+ end
274
+
275
+ router :baz, :lenses => [:passthrough] do
276
+ send_to :foo
277
+ end
278
+ end
279
+ sortah.sort(@email)
280
+ $count.should == 1
281
+ end
282
+
283
+ it "should execute only until the first #send_to call" do
284
+ sortah do
285
+ destination :foo, "foo/"
286
+ router do
287
+ send_to :foo
288
+ throw Exception
289
+ end
290
+ end
291
+ expect { sortah.sort(@email) }.should_not raise_error Exception
292
+ sortah.sort(@email).destination.should == "foo/"
293
+ end
294
+
295
+ describe "#full_destination" do
296
+ it "should return the full path (including the maildir basepath) to which an email will be routed" do
297
+ sortah do
298
+ maildir '/tmp/'
299
+ destination :foo, "foo/"
300
+ router do
301
+ send_to :foo
302
+ end
303
+ end
304
+ sortah.sort(@email).full_destination.should == "/tmp/foo/"
305
+
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end