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.

Files changed (179) hide show
  1. data/Gemfile +30 -12
  2. data/Gemfile.lock +131 -119
  3. data/Procfile +1 -1
  4. data/README.md +1 -1
  5. data/app/assets/images/notifications.png +0 -0
  6. data/app/assets/javascripts/application.js.erb +3 -0
  7. data/app/assets/javascripts/crm_textarea_autocomplete.js +44 -0
  8. data/app/assets/stylesheets/application.css.erb +2 -0
  9. data/app/assets/stylesheets/common.scss +7 -11
  10. data/app/assets/stylesheets/textarea_autocomplete.scss +42 -0
  11. data/app/controllers/admin/application_controller.rb +5 -5
  12. data/app/controllers/admin/field_groups_controller.rb +11 -51
  13. data/app/controllers/admin/fields_controller.rb +13 -59
  14. data/app/controllers/admin/plugins_controller.rb +1 -4
  15. data/app/controllers/admin/settings_controller.rb +0 -4
  16. data/app/controllers/admin/tags_controller.rb +11 -66
  17. data/app/controllers/admin/users_controller.rb +20 -83
  18. data/app/controllers/application_controller.rb +83 -69
  19. data/app/controllers/comments_controller.rb +12 -29
  20. data/app/controllers/emails_controller.rb +1 -5
  21. data/app/controllers/entities/accounts_controller.rb +13 -32
  22. data/app/controllers/entities/campaigns_controller.rb +17 -32
  23. data/app/controllers/entities/contacts_controller.rb +20 -38
  24. data/app/controllers/entities/leads_controller.rb +33 -55
  25. data/app/controllers/entities/opportunities_controller.rb +26 -42
  26. data/app/controllers/entities_controller.rb +92 -83
  27. data/app/controllers/home_controller.rb +1 -10
  28. data/app/controllers/lists_controller.rb +1 -4
  29. data/app/controllers/{entities/tasks_controller.rb → tasks_controller.rb} +21 -32
  30. data/app/controllers/users_controller.rb +6 -5
  31. data/app/helpers/accounts_helper.rb +32 -9
  32. data/app/helpers/application_helper.rb +15 -1
  33. data/app/helpers/campaigns_helper.rb +1 -1
  34. data/app/helpers/comments_helper.rb +11 -1
  35. data/app/helpers/leads_helper.rb +1 -1
  36. data/app/helpers/opportunities_helper.rb +1 -1
  37. data/app/{models/mailers/notifier.rb → mailers/dropbox_mailer.rb} +5 -16
  38. data/app/mailers/subscription_mailer.rb +37 -0
  39. data/{lib/tasks/dropbox.rake → app/mailers/user_mailer.rb} +11 -13
  40. data/app/models/entities/account.rb +3 -1
  41. data/app/models/entities/campaign.rb +3 -1
  42. data/app/models/entities/contact.rb +3 -1
  43. data/app/models/entities/lead.rb +6 -5
  44. data/app/models/entities/opportunity.rb +3 -1
  45. data/app/models/fields/field.rb +1 -1
  46. data/app/models/polymorphic/comment.rb +34 -0
  47. data/app/models/{entities → polymorphic}/task.rb +16 -3
  48. data/app/models/setting.rb +15 -15
  49. data/app/models/users/ability.rb +12 -5
  50. data/app/models/users/user.rb +7 -2
  51. data/app/views/accounts/index.html.haml +1 -1
  52. data/app/views/accounts/index.js.rjs +1 -1
  53. data/app/views/admin/plugins/index.html.haml +1 -7
  54. data/app/views/{shared/auto_complete.html.haml → application/_auto_complete.html.haml} +0 -0
  55. data/app/views/{shared → application}/index.atom.builder +1 -1
  56. data/app/views/{shared → application}/index.rss.builder +1 -1
  57. data/app/views/campaigns/index.html.haml +1 -1
  58. data/app/views/campaigns/index.js.rjs +1 -1
  59. data/app/views/comments/_new.html.haml +6 -0
  60. data/app/views/comments/_subscription_links.html.haml +13 -0
  61. data/app/views/comments/new.js.rjs +2 -0
  62. data/app/views/contacts/_top_section.html.haml +3 -13
  63. data/app/views/contacts/index.html.haml +1 -1
  64. data/app/views/contacts/index.js.rjs +1 -1
  65. data/app/views/{notifier/dropbox_ack_notification.html.haml → dropbox_mailer/dropbox_notification.html.haml} +2 -2
  66. data/app/views/{shared → entities}/attach.js.rjs +1 -1
  67. data/app/views/entities/contacts.js.rjs +1 -1
  68. data/app/views/{shared/discard.rjs → entities/discard.js.rjs} +0 -0
  69. data/app/views/entities/leads.js.rjs +1 -1
  70. data/app/views/entities/opportunities.js.rjs +1 -1
  71. data/app/views/entities/subscription_update.js.rjs +4 -0
  72. data/app/views/entities/versions.js.rjs +1 -1
  73. data/app/views/layouts/_footer.html.haml +1 -1
  74. data/app/views/layouts/application.html.haml +3 -0
  75. data/app/views/leads/_contact.html.haml +1 -0
  76. data/app/views/leads/index.html.haml +1 -1
  77. data/app/views/leads/index.js.rjs +1 -1
  78. data/app/views/opportunities/_top_section.html.haml +4 -14
  79. data/app/views/opportunities/index.html.haml +1 -1
  80. data/app/views/opportunities/index.js.rjs +1 -1
  81. data/app/views/subscription_mailer/comment_notification.text.erb +7 -0
  82. data/app/views/{notifier → user_mailer}/password_reset_instructions.html.haml +0 -0
  83. data/config/application.rb +3 -1
  84. data/config/environments/development.rb +1 -1
  85. data/config/environments/test.rb +3 -0
  86. data/config/initializers/action_mailer.rb +8 -5
  87. data/config/initializers/cancan.rb +151 -0
  88. data/config/initializers/constants.rb +1 -0
  89. data/config/initializers/locale.rb +20 -0
  90. data/config/initializers/paper_trail.rb +4 -5
  91. data/config/initializers/relative_url_root.rb +0 -1
  92. data/config/initializers/squeel.rb +5 -0
  93. data/config/locales/cz_fat_free_crm.yml +3 -3
  94. data/config/locales/de.yml +2 -2
  95. data/config/locales/de_fat_free_crm.yml +651 -596
  96. data/config/locales/en-GB_fat_free_crm.yml +3 -3
  97. data/config/locales/en-US_fat_free_crm.yml +13 -3
  98. data/config/locales/es_fat_free_crm.yml +3 -3
  99. data/config/locales/fr-CA_fat_free_crm.yml +3 -3
  100. data/config/locales/fr_fat_free_crm.yml +3 -3
  101. data/config/locales/it_fat_free_crm.yml +3 -3
  102. data/config/locales/pl_fat_free_crm.yml +3 -3
  103. data/config/locales/pt-BR_fat_free_crm.yml +3 -3
  104. data/config/locales/ru_fat_free_crm.yml +3 -3
  105. data/config/locales/sv-SE_fat_free_crm.yml +3 -3
  106. data/config/locales/th_fat_free_crm.yml +3 -3
  107. data/config/routes.rb +10 -0
  108. data/config/settings.default.yml +29 -10
  109. data/config/unicorn.rb +4 -0
  110. data/db/migrate/20111201030535_add_field_groups_klass_name.rb +3 -1
  111. data/db/migrate/20120314080441_add_subscribed_users_to_entities.rb +23 -0
  112. data/db/migrate/20120405080727_change_subscribed_users_to_set.rb +24 -0
  113. data/db/migrate/20120405080742_change_further_subscribed_users_to_set.rb +27 -0
  114. data/db/migrate/20120413034923_add_index_on_versions_item_type.rb +5 -0
  115. data/db/schema.rb +109 -126
  116. data/fat_free_crm.gemspec +12 -18
  117. data/lib/fat_free_crm.rb +0 -1
  118. data/lib/fat_free_crm/core_ext/array.rb +1 -0
  119. data/lib/fat_free_crm/gem_dependencies.rb +1 -0
  120. data/lib/fat_free_crm/mail_processor/base.rb +226 -0
  121. data/lib/fat_free_crm/mail_processor/comment_replies.rb +86 -0
  122. data/lib/fat_free_crm/mail_processor/dropbox.rb +288 -0
  123. data/lib/fat_free_crm/permissions.rb +6 -19
  124. data/lib/fat_free_crm/renderers.rb +0 -8
  125. data/lib/fat_free_crm/tabs.rb +1 -1
  126. data/lib/fat_free_crm/version.rb +1 -1
  127. data/lib/plugins/country_select/lib/country_select.rb +2 -2
  128. data/lib/tasks/mail_processing.rake +60 -0
  129. data/spec/controllers/admin/users_controller_spec.rb +0 -2
  130. data/spec/controllers/{accounts_controller_spec.rb → entities/accounts_controller_spec.rb} +7 -9
  131. data/spec/controllers/{campaigns_controller_spec.rb → entities/campaigns_controller_spec.rb} +7 -7
  132. data/spec/controllers/{contacts_controller_spec.rb → entities/contacts_controller_spec.rb} +5 -9
  133. data/spec/controllers/{leads_controller_spec.rb → entities/leads_controller_spec.rb} +7 -9
  134. data/spec/controllers/{opportunities_controller_spec.rb → entities/opportunities_controller_spec.rb} +8 -15
  135. data/spec/controllers/tasks_controller_spec.rb +1 -5
  136. data/spec/controllers/users_controller_spec.rb +5 -9
  137. data/spec/factories/subscription_factories.rb +6 -0
  138. data/spec/lib/mail_processor/base_spec.rb +164 -0
  139. data/spec/lib/mail_processor/comment_replies_spec.rb +63 -0
  140. data/spec/lib/{dropbox_spec.rb → mail_processor/dropbox_spec.rb} +73 -181
  141. data/spec/lib/mail_processor/sample_emails/dropbox.rb +167 -0
  142. data/spec/mailers/subscription_mailer_spec.rb +17 -0
  143. data/spec/models/{base → entities}/account_contact_spec.rb +0 -0
  144. data/spec/models/{base → entities}/account_opportunity_spec.rb +0 -0
  145. data/spec/models/{base → entities}/account_spec.rb +4 -0
  146. data/spec/models/{base → entities}/campaign_spec.rb +4 -0
  147. data/spec/models/{base → entities}/contact_opportunity_spec.rb +0 -0
  148. data/spec/models/{base → entities}/contact_spec.rb +4 -0
  149. data/spec/models/{base → entities}/lead_spec.rb +4 -0
  150. data/spec/models/{base → entities}/opportunity_spec.rb +4 -0
  151. data/spec/models/polymorphic/comment_spec.rb +15 -0
  152. data/spec/models/{base → polymorphic}/task_spec.rb +124 -30
  153. data/spec/models/polymorphic/version_spec.rb +1 -1
  154. data/spec/shared/controllers.rb +5 -7
  155. data/spec/shared/models.rb +46 -0
  156. data/spec/spec_helper.rb +3 -4
  157. data/spec/support/mail_processor_mocks.rb +30 -0
  158. data/spec/support/uploaded_file.rb +3 -0
  159. data/spec/views/{common → application}/auto_complete.haml_spec.rb +1 -1
  160. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  161. data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  162. data/vendor/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
  163. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  164. data/vendor/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  165. data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  166. data/vendor/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  167. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  168. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  169. data/vendor/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
  170. data/vendor/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
  171. data/vendor/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
  172. data/vendor/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
  173. data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  174. data/vendor/assets/javascripts/textarea_autocomplete.js +605 -0
  175. data/vendor/assets/stylesheets/jquery-ui.custom.css.erb +565 -0
  176. metadata +234 -154
  177. data/config/locales/simple_form.en.yml +0 -24
  178. data/lib/fat_free_crm/dropbox.rb +0 -439
  179. 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.first_name.should == "Billy"
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("/rails.png", "image/png")
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,6 @@
1
+ # Read about factories at https://github.com/thoughtbot/factory_girl
2
+
3
+ FactoryGirl.define do
4
+ factory :subscription do
5
+ end
6
+ end
@@ -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__) + '/../spec_helper')
2
- require File.dirname(__FILE__) + '/dropbox/email_samples'
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 "IMAP Dropbox" do
7
- before(:each) do
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
- it "should be valid email if its contents type is text/plain" do
129
- @email.stub!(:content_type).and_return("text/plain")
130
- @crawler.send(:is_valid?, @email).should == true
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(EMAIL[:first_line])
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(EMAIL[:first_line])
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(EMAIL[:first_line_lead])
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(EMAIL[:first_line_lead])
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(EMAIL[:first_line_contact])
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(EMAIL[:first_line_contact])
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(EMAIL[:plain])
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 "Pipieline: processing recipients (To: recipient, Bcc: dropbox)" do
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(EMAIL[:plain])
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 move on if asset recipients did not match" do
303
- @crawler.should_receive(:with_recipients).twice
304
- @crawler.should_receive(:with_forwarded_recipient).twice
305
- @crawler.run
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 "Pipieline: processing forwarded recipient (To: dropbox)" do
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(EMAIL[:forwarded])
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 "Pipieline: creating recipient if s/he was not found" do
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(EMAIL[:plain])
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(EMAIL[:forwarded])
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