fat_free_crm 0.11.1 → 0.11.2
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.
Potentially problematic release.
This version of fat_free_crm might be problematic. Click here for more details.
- data/Gemfile +30 -12
- data/Gemfile.lock +131 -119
- data/Procfile +1 -1
- data/README.md +1 -1
- data/app/assets/images/notifications.png +0 -0
- data/app/assets/javascripts/application.js.erb +3 -0
- data/app/assets/javascripts/crm_textarea_autocomplete.js +44 -0
- data/app/assets/stylesheets/application.css.erb +2 -0
- data/app/assets/stylesheets/common.scss +7 -11
- data/app/assets/stylesheets/textarea_autocomplete.scss +42 -0
- data/app/controllers/admin/application_controller.rb +5 -5
- data/app/controllers/admin/field_groups_controller.rb +11 -51
- data/app/controllers/admin/fields_controller.rb +13 -59
- data/app/controllers/admin/plugins_controller.rb +1 -4
- data/app/controllers/admin/settings_controller.rb +0 -4
- data/app/controllers/admin/tags_controller.rb +11 -66
- data/app/controllers/admin/users_controller.rb +20 -83
- data/app/controllers/application_controller.rb +83 -69
- data/app/controllers/comments_controller.rb +12 -29
- data/app/controllers/emails_controller.rb +1 -5
- data/app/controllers/entities/accounts_controller.rb +13 -32
- data/app/controllers/entities/campaigns_controller.rb +17 -32
- data/app/controllers/entities/contacts_controller.rb +20 -38
- data/app/controllers/entities/leads_controller.rb +33 -55
- data/app/controllers/entities/opportunities_controller.rb +26 -42
- data/app/controllers/entities_controller.rb +92 -83
- data/app/controllers/home_controller.rb +1 -10
- data/app/controllers/lists_controller.rb +1 -4
- data/app/controllers/{entities/tasks_controller.rb → tasks_controller.rb} +21 -32
- data/app/controllers/users_controller.rb +6 -5
- data/app/helpers/accounts_helper.rb +32 -9
- data/app/helpers/application_helper.rb +15 -1
- data/app/helpers/campaigns_helper.rb +1 -1
- data/app/helpers/comments_helper.rb +11 -1
- data/app/helpers/leads_helper.rb +1 -1
- data/app/helpers/opportunities_helper.rb +1 -1
- data/app/{models/mailers/notifier.rb → mailers/dropbox_mailer.rb} +5 -16
- data/app/mailers/subscription_mailer.rb +37 -0
- data/{lib/tasks/dropbox.rake → app/mailers/user_mailer.rb} +11 -13
- data/app/models/entities/account.rb +3 -1
- data/app/models/entities/campaign.rb +3 -1
- data/app/models/entities/contact.rb +3 -1
- data/app/models/entities/lead.rb +6 -5
- data/app/models/entities/opportunity.rb +3 -1
- data/app/models/fields/field.rb +1 -1
- data/app/models/polymorphic/comment.rb +34 -0
- data/app/models/{entities → polymorphic}/task.rb +16 -3
- data/app/models/setting.rb +15 -15
- data/app/models/users/ability.rb +12 -5
- data/app/models/users/user.rb +7 -2
- data/app/views/accounts/index.html.haml +1 -1
- data/app/views/accounts/index.js.rjs +1 -1
- data/app/views/admin/plugins/index.html.haml +1 -7
- data/app/views/{shared/auto_complete.html.haml → application/_auto_complete.html.haml} +0 -0
- data/app/views/{shared → application}/index.atom.builder +1 -1
- data/app/views/{shared → application}/index.rss.builder +1 -1
- data/app/views/campaigns/index.html.haml +1 -1
- data/app/views/campaigns/index.js.rjs +1 -1
- data/app/views/comments/_new.html.haml +6 -0
- data/app/views/comments/_subscription_links.html.haml +13 -0
- data/app/views/comments/new.js.rjs +2 -0
- data/app/views/contacts/_top_section.html.haml +3 -13
- data/app/views/contacts/index.html.haml +1 -1
- data/app/views/contacts/index.js.rjs +1 -1
- data/app/views/{notifier/dropbox_ack_notification.html.haml → dropbox_mailer/dropbox_notification.html.haml} +2 -2
- data/app/views/{shared → entities}/attach.js.rjs +1 -1
- data/app/views/entities/contacts.js.rjs +1 -1
- data/app/views/{shared/discard.rjs → entities/discard.js.rjs} +0 -0
- data/app/views/entities/leads.js.rjs +1 -1
- data/app/views/entities/opportunities.js.rjs +1 -1
- data/app/views/entities/subscription_update.js.rjs +4 -0
- data/app/views/entities/versions.js.rjs +1 -1
- data/app/views/layouts/_footer.html.haml +1 -1
- data/app/views/layouts/application.html.haml +3 -0
- data/app/views/leads/_contact.html.haml +1 -0
- data/app/views/leads/index.html.haml +1 -1
- data/app/views/leads/index.js.rjs +1 -1
- data/app/views/opportunities/_top_section.html.haml +4 -14
- data/app/views/opportunities/index.html.haml +1 -1
- data/app/views/opportunities/index.js.rjs +1 -1
- data/app/views/subscription_mailer/comment_notification.text.erb +7 -0
- data/app/views/{notifier → user_mailer}/password_reset_instructions.html.haml +0 -0
- data/config/application.rb +3 -1
- data/config/environments/development.rb +1 -1
- data/config/environments/test.rb +3 -0
- data/config/initializers/action_mailer.rb +8 -5
- data/config/initializers/cancan.rb +151 -0
- data/config/initializers/constants.rb +1 -0
- data/config/initializers/locale.rb +20 -0
- data/config/initializers/paper_trail.rb +4 -5
- data/config/initializers/relative_url_root.rb +0 -1
- data/config/initializers/squeel.rb +5 -0
- data/config/locales/cz_fat_free_crm.yml +3 -3
- data/config/locales/de.yml +2 -2
- data/config/locales/de_fat_free_crm.yml +651 -596
- data/config/locales/en-GB_fat_free_crm.yml +3 -3
- data/config/locales/en-US_fat_free_crm.yml +13 -3
- data/config/locales/es_fat_free_crm.yml +3 -3
- data/config/locales/fr-CA_fat_free_crm.yml +3 -3
- data/config/locales/fr_fat_free_crm.yml +3 -3
- data/config/locales/it_fat_free_crm.yml +3 -3
- data/config/locales/pl_fat_free_crm.yml +3 -3
- data/config/locales/pt-BR_fat_free_crm.yml +3 -3
- data/config/locales/ru_fat_free_crm.yml +3 -3
- data/config/locales/sv-SE_fat_free_crm.yml +3 -3
- data/config/locales/th_fat_free_crm.yml +3 -3
- data/config/routes.rb +10 -0
- data/config/settings.default.yml +29 -10
- data/config/unicorn.rb +4 -0
- data/db/migrate/20111201030535_add_field_groups_klass_name.rb +3 -1
- data/db/migrate/20120314080441_add_subscribed_users_to_entities.rb +23 -0
- data/db/migrate/20120405080727_change_subscribed_users_to_set.rb +24 -0
- data/db/migrate/20120405080742_change_further_subscribed_users_to_set.rb +27 -0
- data/db/migrate/20120413034923_add_index_on_versions_item_type.rb +5 -0
- data/db/schema.rb +109 -126
- data/fat_free_crm.gemspec +12 -18
- data/lib/fat_free_crm.rb +0 -1
- data/lib/fat_free_crm/core_ext/array.rb +1 -0
- data/lib/fat_free_crm/gem_dependencies.rb +1 -0
- data/lib/fat_free_crm/mail_processor/base.rb +226 -0
- data/lib/fat_free_crm/mail_processor/comment_replies.rb +86 -0
- data/lib/fat_free_crm/mail_processor/dropbox.rb +288 -0
- data/lib/fat_free_crm/permissions.rb +6 -19
- data/lib/fat_free_crm/renderers.rb +0 -8
- data/lib/fat_free_crm/tabs.rb +1 -1
- data/lib/fat_free_crm/version.rb +1 -1
- data/lib/plugins/country_select/lib/country_select.rb +2 -2
- data/lib/tasks/mail_processing.rake +60 -0
- data/spec/controllers/admin/users_controller_spec.rb +0 -2
- data/spec/controllers/{accounts_controller_spec.rb → entities/accounts_controller_spec.rb} +7 -9
- data/spec/controllers/{campaigns_controller_spec.rb → entities/campaigns_controller_spec.rb} +7 -7
- data/spec/controllers/{contacts_controller_spec.rb → entities/contacts_controller_spec.rb} +5 -9
- data/spec/controllers/{leads_controller_spec.rb → entities/leads_controller_spec.rb} +7 -9
- data/spec/controllers/{opportunities_controller_spec.rb → entities/opportunities_controller_spec.rb} +8 -15
- data/spec/controllers/tasks_controller_spec.rb +1 -5
- data/spec/controllers/users_controller_spec.rb +5 -9
- data/spec/factories/subscription_factories.rb +6 -0
- data/spec/lib/mail_processor/base_spec.rb +164 -0
- data/spec/lib/mail_processor/comment_replies_spec.rb +63 -0
- data/spec/lib/{dropbox_spec.rb → mail_processor/dropbox_spec.rb} +73 -181
- data/spec/lib/mail_processor/sample_emails/dropbox.rb +167 -0
- data/spec/mailers/subscription_mailer_spec.rb +17 -0
- data/spec/models/{base → entities}/account_contact_spec.rb +0 -0
- data/spec/models/{base → entities}/account_opportunity_spec.rb +0 -0
- data/spec/models/{base → entities}/account_spec.rb +4 -0
- data/spec/models/{base → entities}/campaign_spec.rb +4 -0
- data/spec/models/{base → entities}/contact_opportunity_spec.rb +0 -0
- data/spec/models/{base → entities}/contact_spec.rb +4 -0
- data/spec/models/{base → entities}/lead_spec.rb +4 -0
- data/spec/models/{base → entities}/opportunity_spec.rb +4 -0
- data/spec/models/polymorphic/comment_spec.rb +15 -0
- data/spec/models/{base → polymorphic}/task_spec.rb +124 -30
- data/spec/models/polymorphic/version_spec.rb +1 -1
- data/spec/shared/controllers.rb +5 -7
- data/spec/shared/models.rb +46 -0
- data/spec/spec_helper.rb +3 -4
- data/spec/support/mail_processor_mocks.rb +30 -0
- data/spec/support/uploaded_file.rb +3 -0
- data/spec/views/{common → application}/auto_complete.haml_spec.rb +1 -1
- data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
- data/vendor/assets/javascripts/textarea_autocomplete.js +605 -0
- data/vendor/assets/stylesheets/jquery-ui.custom.css.erb +565 -0
- metadata +234 -154
- data/config/locales/simple_form.en.yml +0 -24
- data/lib/fat_free_crm/dropbox.rb +0 -439
- data/spec/lib/dropbox/email_samples.rb +0 -77
@@ -8,7 +8,6 @@ describe TasksController do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def produce_tasks(user, view)
|
11
|
-
#~ Time.zone = 'UTC'
|
12
11
|
settings = (view != "completed" ? Setting.task_bucket : Setting.task_completed)
|
13
12
|
|
14
13
|
settings.inject({}) do | hash, due |
|
@@ -61,6 +60,7 @@ describe TasksController do
|
|
61
60
|
end
|
62
61
|
|
63
62
|
TASK_STATUSES.each do |view|
|
63
|
+
|
64
64
|
it "should expose all tasks as @tasks and render [index] template for #{view} view" do
|
65
65
|
@tasks = produce_tasks(@current_user, view)
|
66
66
|
|
@@ -117,10 +117,6 @@ describe TasksController do
|
|
117
117
|
describe "responding to GET show" do
|
118
118
|
|
119
119
|
TASK_STATUSES.each do |view|
|
120
|
-
it "should render tasks index for #{view} view (since a task doesn't have landing page)" do
|
121
|
-
get :show, :id => 42, :view => view
|
122
|
-
response.should render_template("tasks/index")
|
123
|
-
end
|
124
120
|
|
125
121
|
it "should render the requested task as JSON for #{view} view" do
|
126
122
|
Task.stub_chain(:tracked_by, :find).and_return(task = mock("Task"))
|
@@ -74,7 +74,7 @@ describe UsersController do
|
|
74
74
|
|
75
75
|
describe "if user is allowed to sign up" do
|
76
76
|
it "should expose a new user as @user and render [new] template" do
|
77
|
-
controller.should_receive(:can_signup?).and_return(true)
|
77
|
+
@controller.should_receive(:can_signup?).and_return(true)
|
78
78
|
@user = FactoryGirl.build(:user)
|
79
79
|
User.stub!(:new).and_return(@user)
|
80
80
|
|
@@ -86,7 +86,7 @@ describe UsersController do
|
|
86
86
|
|
87
87
|
describe "if user is not allowed to sign up" do
|
88
88
|
it "should redirect to login_path" do
|
89
|
-
controller.should_receive(:can_signup?).and_return(false)
|
89
|
+
@controller.should_receive(:can_signup?).and_return(false)
|
90
90
|
|
91
91
|
get :new
|
92
92
|
response.should redirect_to(login_path)
|
@@ -139,7 +139,6 @@ describe UsersController do
|
|
139
139
|
flash[:notice].should =~ /approval/
|
140
140
|
response.should redirect_to(login_path)
|
141
141
|
end
|
142
|
-
|
143
142
|
end
|
144
143
|
|
145
144
|
describe "with invalid params" do
|
@@ -152,7 +151,6 @@ describe UsersController do
|
|
152
151
|
response.should render_template("users/new")
|
153
152
|
end
|
154
153
|
end
|
155
|
-
|
156
154
|
end
|
157
155
|
|
158
156
|
# PUT /users/1
|
@@ -168,12 +166,12 @@ describe UsersController do
|
|
168
166
|
|
169
167
|
it "should update user information and render [update] template" do
|
170
168
|
xhr :put, :update, :id => @user.id, :user => { :first_name => "Billy", :last_name => "Bones" }
|
171
|
-
@user.reload
|
169
|
+
@user.reload
|
170
|
+
@user.first_name.should == "Billy"
|
172
171
|
@user.last_name.should == "Bones"
|
173
172
|
assigns[:user].should == @user
|
174
173
|
response.should render_template("users/update")
|
175
174
|
end
|
176
|
-
|
177
175
|
end
|
178
176
|
|
179
177
|
describe "with invalid params" do
|
@@ -184,9 +182,7 @@ describe UsersController do
|
|
184
182
|
assigns[:user].should == @user
|
185
183
|
response.should render_template("users/update")
|
186
184
|
end
|
187
|
-
|
188
185
|
end
|
189
|
-
|
190
186
|
end
|
191
187
|
|
192
188
|
# DELETE /users/1
|
@@ -247,7 +243,7 @@ describe UsersController do
|
|
247
243
|
end
|
248
244
|
|
249
245
|
it "should save the user avatar if it was successfully uploaded and resized" do
|
250
|
-
@image = fixture_file_upload(
|
246
|
+
@image = fixture_file_upload('/rails.png', 'image/png')
|
251
247
|
|
252
248
|
xhr :put, :upload_avatar, :id => @user.id, :avatar => { :image => @image }
|
253
249
|
@user.avatar.should_not == nil
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
require File.dirname(__FILE__) + '/sample_emails/dropbox'
|
3
|
+
|
4
|
+
require "fat_free_crm/mail_processor/base"
|
5
|
+
|
6
|
+
describe FatFreeCRM::MailProcessor::Base do
|
7
|
+
include MockIMAP
|
8
|
+
|
9
|
+
before do
|
10
|
+
@mock_address = "base-mail-processor@example.com"
|
11
|
+
end
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@crawler = FatFreeCRM::MailProcessor::Base.new
|
15
|
+
# MailProcessor::Base doesn't load any settings by default
|
16
|
+
@crawler.instance_variable_set "@settings", {
|
17
|
+
:server => "example.com",
|
18
|
+
:port => "123",
|
19
|
+
:ssl => true,
|
20
|
+
:address => "test@example.com",
|
21
|
+
:user => "test@example.com",
|
22
|
+
:password => "123"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
#------------------------------------------------------------------------------
|
27
|
+
describe "Connecting to the IMAP server" do
|
28
|
+
it "should connect to the IMAP server and login as user, and select folder" do
|
29
|
+
mock_imap
|
30
|
+
@imap.should_receive(:login).once.with(@settings[:user], @settings[:password])
|
31
|
+
@imap.should_receive(:select).once.with(@settings[:scan_folder])
|
32
|
+
@crawler.send(:connect!)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should connect to the IMAP server, login as user, but not select folder when requested so" do
|
36
|
+
mock_imap
|
37
|
+
@imap.should_receive(:login).once.with(@settings[:user], @settings[:password])
|
38
|
+
@imap.should_not_receive(:select).with(@settings[:scan_folder])
|
39
|
+
@crawler.send(:connect!, :setup => true)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should raise the error if connection fails" do
|
43
|
+
Net::IMAP.should_receive(:new).and_raise(SocketError) # No mocks this time! :-)
|
44
|
+
@crawler.send(:connect!).should == nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#------------------------------------------------------------------------------
|
49
|
+
describe "Disconnecting from the IMAP server" do
|
50
|
+
it "should logout and diconnect" do
|
51
|
+
mock_connect
|
52
|
+
mock_disconnect
|
53
|
+
@imap.should_receive(:logout).once
|
54
|
+
@imap.should_receive(:disconnect).once
|
55
|
+
|
56
|
+
@crawler.send(:connect!)
|
57
|
+
@crawler.send(:disconnect!)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#------------------------------------------------------------------------------
|
62
|
+
describe "Discarding a message" do
|
63
|
+
before(:each) do
|
64
|
+
mock_connect
|
65
|
+
@uid = mock
|
66
|
+
@crawler.send(:connect!)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should copy message to invalid folder if it's set and flag the message as deleted" do
|
70
|
+
@settings[:move_invalid_to_folder] = "invalid"
|
71
|
+
@imap.should_receive(:uid_copy).once.with(@uid, @settings[:move_invalid_to_folder])
|
72
|
+
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Deleted])
|
73
|
+
@crawler.send(:discard, @uid)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not copy message to invalid folder if it's not set and flag the message as deleted" do
|
77
|
+
@settings[:move_invalid_to_folder] = nil
|
78
|
+
@imap.should_not_receive(:uid_copy)
|
79
|
+
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Deleted])
|
80
|
+
@crawler.send(:discard, @uid)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#------------------------------------------------------------------------------
|
85
|
+
describe "Archiving a message" do
|
86
|
+
before(:each) do
|
87
|
+
mock_connect
|
88
|
+
@uid = mock
|
89
|
+
@crawler.send(:connect!)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should copy message to archive folder if it's set and flag the message as seen" do
|
93
|
+
@settings[:move_to_folder] = "processed"
|
94
|
+
@imap.should_receive(:uid_copy).once.with(@uid, @settings[:move_to_folder])
|
95
|
+
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Seen])
|
96
|
+
@crawler.send(:archive, @uid)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should not copy message to archive folder if it's not set and flag the message as seen" do
|
100
|
+
@settings[:move_to_folder] = nil
|
101
|
+
@imap.should_not_receive(:uid_copy)
|
102
|
+
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Seen])
|
103
|
+
@crawler.send(:archive, @uid)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
#------------------------------------------------------------------------------
|
108
|
+
describe "Validating email" do
|
109
|
+
before(:each) do
|
110
|
+
@email = mock
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should be valid email if its contents type is text/plain" do
|
114
|
+
@email.stub!(:content_type).and_return("text/plain")
|
115
|
+
@crawler.send(:is_valid?, @email).should == true
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should be invalid email if its contents type is not text/plain" do
|
119
|
+
@email.stub!(:content_type).and_return("text/html")
|
120
|
+
@crawler.send(:is_valid?, @email).should == false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#------------------------------------------------------------------------------
|
125
|
+
describe "Finding email sender among users" do
|
126
|
+
before(:each) do
|
127
|
+
@from = [ "Aaron@Example.Com", "Ben@Example.com" ]
|
128
|
+
@email = mock
|
129
|
+
@email.stub!(:from).and_return(@from)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should find non-suspended user that matches From: field" do
|
133
|
+
@user = FactoryGirl.create(:user, :email => @from.first, :suspended_at => nil)
|
134
|
+
@crawler.send(:sent_from_known_user?, @email).should == true
|
135
|
+
@crawler.instance_variable_get("@sender").should == @user
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should not find user if his email doesn't match From: field" do
|
139
|
+
FactoryGirl.create(:user, :email => "nobody@example.com")
|
140
|
+
@crawler.send(:sent_from_known_user?, @email).should == false
|
141
|
+
@crawler.instance_variable_get("@sender").should == nil
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should not find user if his email matches From: field but is suspended" do
|
145
|
+
FactoryGirl.create(:user, :email => @from.first, :suspended_at => Time.now)
|
146
|
+
@crawler.send(:sent_from_known_user?, @email).should == false
|
147
|
+
@crawler.instance_variable_get("@sender").should == nil
|
148
|
+
end
|
149
|
+
|
150
|
+
#------------------------------------------------------------------------------
|
151
|
+
describe "Extracting plain text body" do
|
152
|
+
|
153
|
+
it "should extract text from multipart text/plain" do
|
154
|
+
text = @crawler.send(:plain_text_body, Mail.new(DROPBOX_EMAILS[:plain]))
|
155
|
+
text.should be_present
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should extract text and strip tags from multipart text/html" do
|
159
|
+
text = @crawler.send(:plain_text_body, Mail.new(DROPBOX_EMAILS[:multipart]))
|
160
|
+
text.should eql('Hello,')
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
require "fat_free_crm/mail_processor/comment_replies"
|
4
|
+
|
5
|
+
describe FatFreeCRM::MailProcessor::CommentReplies do
|
6
|
+
include MockIMAP
|
7
|
+
|
8
|
+
before do
|
9
|
+
@mock_address = "crm-comment@example.com"
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
@crawler = FatFreeCRM::MailProcessor::CommentReplies.new
|
14
|
+
end
|
15
|
+
|
16
|
+
#------------------------------------------------------------------------------
|
17
|
+
describe "Processing new emails" do
|
18
|
+
before do
|
19
|
+
FactoryGirl.create(:user, :email => "aaron@example.com")
|
20
|
+
end
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
mock_connect
|
24
|
+
mock_disconnect
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should attach a new comment to a contact" do
|
28
|
+
@contact = FactoryGirl.create(:contact)
|
29
|
+
comment_reply = "This is a new comment reply via email"
|
30
|
+
|
31
|
+
mail = Mail.new :from => "Aaron Assembler <aaron@example.com>",
|
32
|
+
:to => "FFCRM Comments <crm-commment@example.com>",
|
33
|
+
:subject => "[contact:#{@contact.id}] Test Contact",
|
34
|
+
:body => comment_reply
|
35
|
+
mock_message mail.to_s
|
36
|
+
|
37
|
+
@crawler.should_receive(:archive).once
|
38
|
+
@crawler.should_not_receive(:with_recipients)
|
39
|
+
@crawler.run
|
40
|
+
|
41
|
+
@contact.comments.size.should == 1
|
42
|
+
@contact.comments.first.comment.should == comment_reply
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should attach a new comment to an opportunity, using the 'op' shortcut in subject" do
|
46
|
+
@opportunity = FactoryGirl.create(:opportunity)
|
47
|
+
comment_reply = "This is a new comment reply via email"
|
48
|
+
|
49
|
+
mail = Mail.new :from => "Aaron Assembler <aaron@example.com>",
|
50
|
+
:to => "FFCRM Comments <crm-commment@example.com>",
|
51
|
+
:subject => "[op:#{@opportunity.id}] Test Opportunity",
|
52
|
+
:body => comment_reply
|
53
|
+
mock_message mail.to_s
|
54
|
+
|
55
|
+
@crawler.should_receive(:archive).once
|
56
|
+
@crawler.should_not_receive(:with_recipients)
|
57
|
+
@crawler.run
|
58
|
+
|
59
|
+
@opportunity.comments.size.should == 1
|
60
|
+
@opportunity.comments.first.comment.should == comment_reply
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,166 +1,14 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '
|
2
|
-
require File.dirname(__FILE__) + '/dropbox
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
require File.dirname(__FILE__) + '/sample_emails/dropbox'
|
3
3
|
|
4
|
-
require "fat_free_crm/dropbox"
|
4
|
+
require "fat_free_crm/mail_processor/dropbox"
|
5
5
|
|
6
|
-
describe
|
7
|
-
|
8
|
-
@crawler = FatFreeCRM::Dropbox.new
|
9
|
-
@crawler.stub!("expunge!").and_return(true)
|
10
|
-
end
|
11
|
-
|
12
|
-
def mock_imap
|
13
|
-
@imap = mock
|
14
|
-
@settings = @crawler.instance_variable_get("@settings")
|
15
|
-
@settings[:address] = "dropbox@example.com"
|
16
|
-
Net::IMAP.stub!(:new).with(@settings[:server], @settings[:port], @settings[:ssl]).and_return(@imap)
|
17
|
-
end
|
18
|
-
|
19
|
-
def mock_connect
|
20
|
-
mock_imap
|
21
|
-
@imap.stub!(:login).and_return(true)
|
22
|
-
@imap.stub!(:select).and_return(true)
|
23
|
-
end
|
24
|
-
|
25
|
-
def mock_disconnect
|
26
|
-
@imap.stub!(:disconnected?).and_return(false)
|
27
|
-
@imap.stub!(:logout).and_return(true)
|
28
|
-
@imap.stub!(:disconnect).and_return(true)
|
29
|
-
end
|
30
|
-
|
31
|
-
def mock_message(body = EMAIL[:plain])
|
32
|
-
@fetch_data = mock
|
33
|
-
@fetch_data.stub!(:attr).and_return("RFC822" => body)
|
34
|
-
@imap.stub!(:uid_search).and_return([ :uid ])
|
35
|
-
@imap.stub!(:uid_fetch).and_return([ @fetch_data ])
|
36
|
-
@imap.stub!(:uid_copy).and_return(true)
|
37
|
-
@imap.stub!(:uid_store).and_return(true)
|
38
|
-
body
|
39
|
-
end
|
40
|
-
|
41
|
-
#------------------------------------------------------------------------------
|
42
|
-
describe "Connecting to the IMAP server" do
|
43
|
-
it "should connect to the IMAP server and login as user, and select folder" do
|
44
|
-
mock_imap
|
45
|
-
@imap.should_receive(:login).once.with(@settings[:user], @settings[:password])
|
46
|
-
@imap.should_receive(:select).once.with(@settings[:scan_folder])
|
47
|
-
@crawler.send(:connect!)
|
48
|
-
end
|
49
|
-
|
50
|
-
it "should connect to the IMAP server, login as user, but not select folder when requested so" do
|
51
|
-
mock_imap
|
52
|
-
@imap.should_receive(:login).once.with(@settings[:user], @settings[:password])
|
53
|
-
@imap.should_not_receive(:select).with(@settings[:scan_folder])
|
54
|
-
@crawler.send(:connect!, :setup => true)
|
55
|
-
end
|
56
|
-
|
57
|
-
it "should raise the error if connection fails" do
|
58
|
-
Net::IMAP.should_receive(:new).and_raise(SocketError) # No mocks this time! :-)
|
59
|
-
@crawler.send(:connect!).should == nil
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
#------------------------------------------------------------------------------
|
64
|
-
describe "Disconnecting from the IMAP server" do
|
65
|
-
it "should logout and diconnect" do
|
66
|
-
mock_connect
|
67
|
-
mock_disconnect
|
68
|
-
@imap.should_receive(:logout).once
|
69
|
-
@imap.should_receive(:disconnect).once
|
70
|
-
|
71
|
-
@crawler.send(:connect!)
|
72
|
-
@crawler.send(:disconnect!)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
#------------------------------------------------------------------------------
|
77
|
-
describe "Discarding a message" do
|
78
|
-
before(:each) do
|
79
|
-
mock_connect
|
80
|
-
@uid = mock
|
81
|
-
@crawler.send(:connect!)
|
82
|
-
end
|
83
|
-
|
84
|
-
it "should copy message to invalid folder if it's set and flag the message as deleted" do
|
85
|
-
@settings[:move_invalid_to_folder] = "invalid"
|
86
|
-
@imap.should_receive(:uid_copy).once.with(@uid, @settings[:move_invalid_to_folder])
|
87
|
-
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Deleted])
|
88
|
-
@crawler.send(:discard, @uid)
|
89
|
-
end
|
90
|
-
|
91
|
-
it "should not copy message to invalid folder if it's not set and flag the message as deleted" do
|
92
|
-
@settings[:move_invalid_to_folder] = nil
|
93
|
-
@imap.should_not_receive(:uid_copy)
|
94
|
-
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Deleted])
|
95
|
-
@crawler.send(:discard, @uid)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
#------------------------------------------------------------------------------
|
100
|
-
describe "Archiving a message" do
|
101
|
-
before(:each) do
|
102
|
-
mock_connect
|
103
|
-
@uid = mock
|
104
|
-
@crawler.send(:connect!)
|
105
|
-
end
|
106
|
-
|
107
|
-
it "should copy message to archive folder if it's set and flag the message as seen" do
|
108
|
-
@settings[:move_to_folder] = "processed"
|
109
|
-
@imap.should_receive(:uid_copy).once.with(@uid, @settings[:move_to_folder])
|
110
|
-
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Seen])
|
111
|
-
@crawler.send(:archive, @uid)
|
112
|
-
end
|
113
|
-
|
114
|
-
it "should not copy message to archive folder if it's not set and flag the message as seen" do
|
115
|
-
@settings[:move_to_folder] = nil
|
116
|
-
@imap.should_not_receive(:uid_copy)
|
117
|
-
@imap.should_receive(:uid_store).once.with(@uid, "+FLAGS", [:Seen])
|
118
|
-
@crawler.send(:archive, @uid)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
#------------------------------------------------------------------------------
|
123
|
-
describe "Validating email" do
|
124
|
-
before(:each) do
|
125
|
-
@email = mock
|
126
|
-
end
|
6
|
+
describe FatFreeCRM::MailProcessor::Dropbox do
|
7
|
+
include MockIMAP
|
127
8
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
it "should be invalid email if its contents type is not text/plain" do
|
134
|
-
@email.stub!(:content_type).and_return("text/html")
|
135
|
-
@crawler.send(:is_valid?, @email).should == false
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
#------------------------------------------------------------------------------
|
140
|
-
describe "Finding email sender among users" do
|
141
|
-
before(:each) do
|
142
|
-
@from = [ "Aaron@Example.Com", "Ben@Example.com" ]
|
143
|
-
@email = mock
|
144
|
-
@email.stub!(:from).and_return(@from)
|
145
|
-
end
|
146
|
-
|
147
|
-
it "should find non-suspended user that matches From: field" do
|
148
|
-
@user = FactoryGirl.create(:user, :email => @from.first, :suspended_at => nil)
|
149
|
-
@crawler.send(:sent_from_known_user?, @email).should == true
|
150
|
-
@crawler.instance_variable_get("@sender").should == @user
|
151
|
-
end
|
152
|
-
|
153
|
-
it "should not find user if his email doesn't match From: field" do
|
154
|
-
FactoryGirl.create(:user, :email => "nobody@example.com")
|
155
|
-
@crawler.send(:sent_from_known_user?, @email).should == false
|
156
|
-
@crawler.instance_variable_get("@sender").should == nil
|
157
|
-
end
|
158
|
-
|
159
|
-
it "should not find user if his email matches From: field but is suspended" do
|
160
|
-
FactoryGirl.create(:user, :email => @from.first, :suspended_at => Time.now)
|
161
|
-
@crawler.send(:sent_from_known_user?, @email).should == false
|
162
|
-
@crawler.instance_variable_get("@sender").should == nil
|
163
|
-
end
|
9
|
+
before(:each) do
|
10
|
+
@mock_address = "dropbox@example.com"
|
11
|
+
@crawler = FatFreeCRM::MailProcessor::Dropbox.new
|
164
12
|
end
|
165
13
|
|
166
14
|
#------------------------------------------------------------------------------
|
@@ -168,7 +16,7 @@ describe "IMAP Dropbox" do
|
|
168
16
|
before(:each) do
|
169
17
|
mock_connect
|
170
18
|
mock_disconnect
|
171
|
-
mock_message
|
19
|
+
mock_message(DROPBOX_EMAILS[:plain])
|
172
20
|
end
|
173
21
|
|
174
22
|
it "should discard a message if it's invalid" do
|
@@ -203,7 +51,7 @@ describe "IMAP Dropbox" do
|
|
203
51
|
end
|
204
52
|
|
205
53
|
it "should find the named asset and attach the email message" do
|
206
|
-
mock_message(
|
54
|
+
mock_message(DROPBOX_EMAILS[:first_line])
|
207
55
|
@campaign = FactoryGirl.create(:campaign, :name => "Got milk!?")
|
208
56
|
@crawler.should_receive(:archive).once
|
209
57
|
@crawler.should_not_receive(:with_recipients)
|
@@ -214,7 +62,7 @@ describe "IMAP Dropbox" do
|
|
214
62
|
end
|
215
63
|
|
216
64
|
it "should create the named asset and attach the email message" do
|
217
|
-
mock_message(
|
65
|
+
mock_message(DROPBOX_EMAILS[:first_line])
|
218
66
|
@crawler.should_receive(:archive).once
|
219
67
|
@crawler.should_not_receive(:with_recipients)
|
220
68
|
@crawler.run
|
@@ -226,7 +74,7 @@ describe "IMAP Dropbox" do
|
|
226
74
|
end
|
227
75
|
|
228
76
|
it "should find the lead and attach the email message" do
|
229
|
-
mock_message(
|
77
|
+
mock_message(DROPBOX_EMAILS[:first_line_lead])
|
230
78
|
@lead = FactoryGirl.create(:lead, :first_name => "Cindy", :last_name => "Cluster")
|
231
79
|
@crawler.should_receive(:archive).once
|
232
80
|
@crawler.should_not_receive(:with_recipients)
|
@@ -237,7 +85,7 @@ describe "IMAP Dropbox" do
|
|
237
85
|
end
|
238
86
|
|
239
87
|
it "should create the lead and attach the email message" do
|
240
|
-
mock_message(
|
88
|
+
mock_message(DROPBOX_EMAILS[:first_line_lead])
|
241
89
|
@crawler.should_receive(:archive).once
|
242
90
|
@crawler.should_not_receive(:with_recipients)
|
243
91
|
@crawler.run
|
@@ -250,7 +98,7 @@ describe "IMAP Dropbox" do
|
|
250
98
|
end
|
251
99
|
|
252
100
|
it "should find the contact and attach the email message" do
|
253
|
-
mock_message(
|
101
|
+
mock_message(DROPBOX_EMAILS[:first_line_contact])
|
254
102
|
@contact = FactoryGirl.create(:contact, :first_name => "Cindy", :last_name => "Cluster")
|
255
103
|
@crawler.should_receive(:archive).once
|
256
104
|
@crawler.should_not_receive(:with_recipients)
|
@@ -261,7 +109,7 @@ describe "IMAP Dropbox" do
|
|
261
109
|
end
|
262
110
|
|
263
111
|
it "should create the contact and attach the email message" do
|
264
|
-
mock_message(
|
112
|
+
mock_message(DROPBOX_EMAILS[:first_line_contact])
|
265
113
|
@crawler.should_receive(:archive).once
|
266
114
|
@crawler.should_not_receive(:with_recipients)
|
267
115
|
@crawler.run
|
@@ -273,7 +121,7 @@ describe "IMAP Dropbox" do
|
|
273
121
|
end
|
274
122
|
|
275
123
|
it "should move on if first line has no keyword" do
|
276
|
-
mock_message(
|
124
|
+
mock_message(DROPBOX_EMAILS[:plain])
|
277
125
|
@crawler.should_receive(:with_recipients).twice
|
278
126
|
@crawler.should_receive(:with_forwarded_recipient).twice
|
279
127
|
@crawler.run
|
@@ -281,11 +129,11 @@ describe "IMAP Dropbox" do
|
|
281
129
|
end
|
282
130
|
|
283
131
|
#------------------------------------------------------------------------------
|
284
|
-
describe "
|
132
|
+
describe "Pipeline: processing recipients (To: recipient, Bcc: dropbox)" do
|
285
133
|
before(:each) do
|
286
134
|
mock_connect
|
287
135
|
mock_disconnect
|
288
|
-
mock_message(
|
136
|
+
mock_message(DROPBOX_EMAILS[:plain])
|
289
137
|
FactoryGirl.create(:user, :email => "aaron@example.com")
|
290
138
|
end
|
291
139
|
|
@@ -299,20 +147,23 @@ describe "IMAP Dropbox" do
|
|
299
147
|
@lead.emails.first.mediator.should == @lead
|
300
148
|
end
|
301
149
|
|
302
|
-
it "should
|
303
|
-
@crawler.should_receive(:
|
304
|
-
@crawler.
|
305
|
-
|
150
|
+
it "should create the asset and attach the email message" do
|
151
|
+
@crawler.should_receive(:archive).once
|
152
|
+
lambda { @crawler.run }.should change(Contact, :count).by(1)
|
153
|
+
|
154
|
+
@contact = Contact.last
|
155
|
+
@contact.emails.size.should == 1
|
156
|
+
@contact.emails.first.mediator.should == @contact
|
306
157
|
end
|
307
158
|
end
|
308
159
|
|
309
160
|
#------------------------------------------------------------------------------
|
310
|
-
describe "
|
161
|
+
describe "Pipeline: processing forwarded recipient (To: dropbox)" do
|
311
162
|
before(:each) do
|
312
163
|
mock_connect
|
313
164
|
mock_disconnect
|
314
165
|
FactoryGirl.create(:user, :email => "aaron@example.com")
|
315
|
-
mock_message(
|
166
|
+
mock_message(DROPBOX_EMAILS[:forwarded])
|
316
167
|
end
|
317
168
|
|
318
169
|
it "should find the asset and attach the email message" do
|
@@ -352,7 +203,33 @@ describe "IMAP Dropbox" do
|
|
352
203
|
end
|
353
204
|
|
354
205
|
#------------------------------------------------------------------------------
|
355
|
-
describe "
|
206
|
+
describe "Pipeline: processing forwarded recipient from email sent to dropbox alias address" do
|
207
|
+
before(:each) do
|
208
|
+
@mock_address = "dropbox-alias-address@example.com"
|
209
|
+
mock_connect
|
210
|
+
mock_disconnect
|
211
|
+
|
212
|
+
@settings = @crawler.instance_variable_get("@settings")
|
213
|
+
@settings[:address_aliases] = ["dropbox@example.com"]
|
214
|
+
|
215
|
+
FactoryGirl.create(:user, :email => "aaron@example.com")
|
216
|
+
mock_message(DROPBOX_EMAILS[:forwarded])
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should not match the dropbox email address if routed to an alias" do
|
220
|
+
@lead = FactoryGirl.create(:lead, :email => "ben@example.com", :access => "Public")
|
221
|
+
@lead_dropbox = FactoryGirl.create(:lead, :email => "dropbox@example.com", :access => "Public")
|
222
|
+
|
223
|
+
@crawler.should_receive(:archive).once
|
224
|
+
@crawler.run
|
225
|
+
|
226
|
+
@lead_dropbox.emails.size.should == 0
|
227
|
+
@lead.emails.size.should == 1
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
#------------------------------------------------------------------------------
|
232
|
+
describe "Pipeline: creating recipient if s/he was not found" do
|
356
233
|
before(:each) do
|
357
234
|
mock_connect
|
358
235
|
mock_disconnect
|
@@ -360,7 +237,7 @@ describe "IMAP Dropbox" do
|
|
360
237
|
end
|
361
238
|
|
362
239
|
it "should create a contact from the email recipient (To: recipient, Bcc: dropbox)" do
|
363
|
-
mock_message(
|
240
|
+
mock_message(DROPBOX_EMAILS[:plain])
|
364
241
|
@crawler.should_receive(:archive).once
|
365
242
|
@crawler.run
|
366
243
|
|
@@ -371,7 +248,7 @@ describe "IMAP Dropbox" do
|
|
371
248
|
end
|
372
249
|
|
373
250
|
it "should create a contact from the forwarded email (To: dropbox)" do
|
374
|
-
mock_message(
|
251
|
+
mock_message(DROPBOX_EMAILS[:forwarded])
|
375
252
|
@crawler.should_receive(:archive).once
|
376
253
|
@crawler.run
|
377
254
|
|
@@ -383,7 +260,24 @@ describe "IMAP Dropbox" do
|
|
383
260
|
end
|
384
261
|
|
385
262
|
#------------------------------------------------------------------------------
|
263
|
+
describe "Extracting body" do
|
264
|
+
before do
|
265
|
+
@dropbox = FatFreeCRM::MailProcessor::Dropbox.new
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should extract text from multipart text/plain" do
|
269
|
+
text = @dropbox.send(:plain_text_body, Mail.new(DROPBOX_EMAILS[:plain]))
|
270
|
+
text.should be_present
|
271
|
+
end
|
386
272
|
|
273
|
+
it "should extract text and strip tags from multipart text/html" do
|
274
|
+
text = @dropbox.send(:plain_text_body, Mail.new(DROPBOX_EMAILS[:html]))
|
275
|
+
text.should be_present
|
276
|
+
text.should_not match(/<\/?[^>]*>/)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
#------------------------------------------------------------------------------
|
387
281
|
describe "Default values" do
|
388
282
|
|
389
283
|
describe "'access'" do
|
@@ -404,8 +298,6 @@ describe "IMAP Dropbox" do
|
|
404
298
|
end
|
405
299
|
|
406
300
|
end
|
407
|
-
|
408
301
|
end
|
409
|
-
|
410
302
|
end
|
411
303
|
|