sortah 0.5.0

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