testCRM.py 77.7 KB
Newer Older
1
# -*- coding: utf-8 -*-
2 3
##############################################################################
#
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
# Copyright (c) 2007 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

import unittest
import os
31
import textwrap
32
from unittest import expectedFailure
33

34
from Products.CMFCore.WorkflowCore import WorkflowException
35
from Products.ERP5Type.tests.utils import FileUpload
36
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
Nicolas Delaby's avatar
Nicolas Delaby committed
37
from Products.ERP5OOo.tests.testIngestion import FILENAME_REGULAR_EXPRESSION
Yusei Tahara's avatar
Yusei Tahara committed
38
from Products.ERP5OOo.tests.testIngestion import REFERENCE_REGULAR_EXPRESSION
39 40 41 42 43
from email.header import decode_header
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders, message_from_string
44
from DateTime import DateTime
Yusei Tahara's avatar
Yusei Tahara committed
45

46 47
def makeFilePath(name):
  return os.path.join(os.path.dirname(__file__), 'test_data', 'crm_emails', name)
48

49 50 51
def makeFileUpload(name):
  path = makeFilePath(name)
  return FileUpload(path, name)
52

53
clear_module_name_list = """
54 55 56 57 58 59
campaign_module
event_module
meeting_module
organisation_module
person_module
sale_opportunity_module
60
portal_categories/resource
Nicolas Delaby's avatar
Nicolas Delaby committed
61
portal_categories/function
62
""".strip().splitlines()
63

64 65 66 67
class BaseTestCRM(ERP5TypeTestCase):

  def afterSetUp(self):
    super(BaseTestCRM, self).afterSetUp()
68
    self.portal.MailHost.reset()
69 70

  def beforeTearDown(self):
71
    self.abort()
72
    # clear modules if necessary
73
    for module_name in clear_module_name_list:
74
      module = self.portal.unrestrictedTraverse(module_name)
75
      module.manage_delObjects(list(module.objectIds()))
76
    self.tic()
77 78 79
    super(BaseTestCRM, self).beforeTearDown()

class TestCRM(BaseTestCRM):
80 81 82
  def getTitle(self):
    return "CRM"

83
  def getBusinessTemplateList(self):
84
    return ('erp5_full_text_mroonga_catalog',
85 86 87
            'erp5_core_proxy_field_legacy',
            'erp5_base',
            'erp5_ingestion',
Jérome Perrin's avatar
Jérome Perrin committed
88
            'erp5_pdm',
89
            'erp5_crm',)
90

91 92 93 94 95
  def test_Event_getQuantity(self):
    event_module = self.portal.event_module
    for portal_type in self.portal.getPortalEventTypeList():
      event = event_module.newContent(portal_type=portal_type)
      # quantity on events is 1 by default
96
      self.assertEqual(1, event.getQuantity())
97 98
      # but it can be overriden
      event.setQuantity(321)
99
      self.assertEqual(321, event.getQuantity())
100

Jérome Perrin's avatar
Jérome Perrin committed
101 102 103 104 105 106
  def test_Event_isMovement(self):
    event_module = self.portal.event_module
    for portal_type in self.portal.getPortalEventTypeList():
      event = event_module.newContent(portal_type=portal_type)
      self.assertTrue(event.isMovement(),
        "%s is not a movement" % portal_type)
107

108
  def test_Event_CreateRelatedEvent(self):
109
    # test workflow to create a related event from responded event
110
    event_module = self.portal.event_module
111
    portal_workflow = self.portal.portal_workflow
112
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',)
113 114
    for ptype in [x for x in self.portal.getPortalEventTypeList() if x !=
        'Acknowledgement']:
115 116
      event = event_module.newContent(portal_type=ptype,
                                      follow_up_value=ticket)
117

118
      event.stop()
119 120 121 122 123

      self.assertEqual(len(event.getCausalityRelatedValueList()), 0)

      self.tic()

124 125 126 127 128
      event.Event_createResponse(response_event_portal_type=ptype,
                                 response_event_title='New Title',
                                 response_event_text_content='New Desc',
                                 response_workflow_action='plan',
                                 )
129 130 131 132 133 134 135 136 137

      self.tic()

      self.assertEqual(len(event.getCausalityRelatedValueList()), 1)

      related_event = event.getCausalityRelatedValue()

      self.assertEqual(related_event.getPortalType(), ptype)
      self.assertEqual(related_event.getTitle(), 'New Title')
138
      self.assertEqual(related_event.getTextContent(), 'New Desc')
139
      self.assertEqual(related_event.getFollowUpValue(), ticket)
Nicolas Delaby's avatar
Nicolas Delaby committed
140

141
  def test_Event_CreateRelatedEventUnauthorized(self):
142
    # test that we don't get Unauthorized error when invoking the "Create
143 144
    # Related Event" without add permission on the module,
    # but will get WorkflowException error.
145 146
    event = self.portal.event_module.newContent(portal_type='Letter')
    self.portal.event_module.manage_permission('Add portal content', [], 0)
147 148 149 150 151
    self.assertRaises(WorkflowException,
                      event.Event_createRelatedEvent,
                      portal_type='Letter',
                      title='New Title',
                      description='New Desc')
Nicolas Delaby's avatar
Nicolas Delaby committed
152

153 154
  def test_Ticket_CreateRelatedEvent(self):
    # test action to create a related event from a ticket
155
    event_module_url = self.portal.event_module.absolute_url()
156
    ticket = self.portal.meeting_module.newContent(portal_type='Meeting')
157 158
    for ptype in [x for x in self.portal.getPortalEventTypeList() if x !=
        'Acknowledgement']:
159
      # incoming
160
      ticket.Ticket_newEvent(portal_type=ptype,
161
                             title='Incoming Title',
162
                             event_workflow_action='deliver')
163
      self.tic()
164
      new_event, = ticket.getFollowUpRelatedValueList(portal_type=ptype)
165
      self.assertEqual('delivered', new_event.getSimulationState())
166 167

      # outgoing
168 169 170
      ticket.Ticket_newEvent(portal_type=ptype,
                             title='Outgoing Title',
                             event_workflow_action='plan')
171
      self.tic()
172 173
      new_event, = [event for event in ticket.getFollowUpRelatedValueList(portal_type=ptype) if\
                   event.getTitle() == 'Outgoing Title']
174
      self.assertEqual('planned', new_event.getSimulationState())
175

176 177 178 179 180 181 182
  def test_Ticket_CreateRelatedEventUnauthorized(self):
    # test that we don't get Unauthorized error when invoking the "Create
    # New Event" without add permission on the module
    ticket = self.portal.meeting_module.newContent(portal_type='Meeting')
    self.portal.event_module.manage_permission('Add portal content', [], 0)
    ticket.Ticket_newEvent(portal_type='Letter',
                           title='New Title',
183
                           event_workflow_action='plan')
Nicolas Delaby's avatar
Nicolas Delaby committed
184

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
  def test_Ticket_getArrowItemList(self):
    # test Ticket_getArrowItemList script
    pers1 = self.portal.person_module.newContent(
      portal_type='Person', title='Person 1')
    pers2 = self.portal.person_module.newContent(
      portal_type='Person', title='Person 2')
    ticket = self.portal.meeting_module.newContent(portal_type='Meeting')
    ticket.setDestinationDecisionValue(pers1)
    ticket.setSourceValue(pers1)

    self.assertEqual(
      [('', ''), ('Person 1', pers1.getRelativeUrl())],
      ticket.Ticket_getArrowItemList())

    # logged in user is also returned
    user = self.createUser(
      self.id(), person_kw={"first_name": "John", "last_name": "Doe"})
    self.tic()
    self.portal.acl_users.zodb_roles.assignRoleToPrincipal('Assignee', user.getUserId())
    self.login(user.getUserId())
    self.assertEqual(
      [('', ''),
       ('John Doe', user.getRelativeUrl()),
       ('Person 1', pers1.getRelativeUrl())],
      ticket.Ticket_getArrowItemList())

    # multiple category value are supported
    ticket.setSourceSectionValueList([user, pers2])
    self.assertEqual(
      [('', ''),
       ('John Doe', user.getRelativeUrl()),
       ('Person 1', pers1.getRelativeUrl()),
       ('Person 2', pers2.getRelativeUrl())],
      ticket.Ticket_getArrowItemList())

220
  def checkCreateRelatedEventSelectionParamsOnPersonModule(self, direction):
221
    # create related event from selected persons.
222 223 224 225 226 227 228 229 230 231 232 233 234
    person_module = self.portal.person_module
    pers1 = person_module.newContent(portal_type='Person', title='Pers1')
    pers2 = person_module.newContent(portal_type='Person', title='Pers2')
    pers3 = person_module.newContent(portal_type='Person', title='Pers3')
    self.portal.person_module.view()
    self.portal.portal_selections.setSelectionCheckedUidsFor(
                          'person_module_selection', [])
    self.portal.portal_selections.setSelectionParamsFor(
                          'person_module_selection', dict(title='Pers1'))
    self.tic()
    person_module.PersonModule_newEvent(portal_type='Mail Message',
                                        title='The Event Title',
                                        description='The Event Descr.',
235
                                        direction=direction,
236 237 238 239
                                        selection_name='person_module_selection',
                                        follow_up='',
                                        text_content='Event Content',
                                        form_id='PersonModule_viewPersonList')
240

241 242
    self.tic()

243 244 245 246 247
    if direction == "outgoing":
      getter_id = "getDestinationRelatedValue"
    elif direction == "incoming":
      getter_id = "getSourceRelatedValue"
    related_event = getattr(pers1, getter_id)(portal_type='Mail Message')
248
    self.assertNotEquals(None, related_event)
249 250 251
    self.assertEqual('The Event Title', related_event.getTitle())
    self.assertEqual('The Event Descr.', related_event.getDescription())
    self.assertEqual('Event Content', related_event.getTextContent())
252 253

    for person in (pers2, pers3):
254
      self.assertEqual(None, getattr(person, getter_id)(
255 256
                                       portal_type='Mail Message'))

257 258 259 260 261 262
  def test_PersonModule_CreateOutgoingRelatedEventSelectionParams(self):
    self.checkCreateRelatedEventSelectionParamsOnPersonModule('outgoing')

  def test_PersonModule_CreateIncomingRelatedEventSelectionParams(self):
    self.checkCreateRelatedEventSelectionParamsOnPersonModule('incoming')

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
  def test_PersonModule_CreateRelatedEventCheckedUid(self):
    # create related event from selected persons.
    person_module = self.portal.person_module
    pers1 = person_module.newContent(portal_type='Person', title='Pers1')
    pers2 = person_module.newContent(portal_type='Person', title='Pers2')
    pers3 = person_module.newContent(portal_type='Person', title='Pers3')
    self.portal.person_module.view()
    self.portal.portal_selections.setSelectionCheckedUidsFor(
          'person_module_selection',
          [pers1.getUid(), pers2.getUid()])
    self.tic()
    person_module.PersonModule_newEvent(portal_type='Mail Message',
                                        title='The Event Title',
                                        description='The Event Descr.',
                                        direction='outgoing',
                                        selection_name='person_module_selection',
                                        follow_up='',
                                        text_content='Event Content',
                                        form_id='PersonModule_viewPersonList')

    self.tic()

    for person in (pers1, pers2):
      related_event = person.getDestinationRelatedValue(
                            portal_type='Mail Message')
      self.assertNotEquals(None, related_event)
289 290 291
      self.assertEqual('The Event Title', related_event.getTitle())
      self.assertEqual('The Event Descr.', related_event.getDescription())
      self.assertEqual('Event Content', related_event.getTextContent())
292

293
    self.assertEqual(None, pers3.getDestinationRelatedValue(
294 295
                                portal_type='Mail Message'))

296 297 298 299 300
  def test_SaleOpportunitySold(self):
    # test the workflow of sale opportunities, when the sale opportunity is
    # finaly sold
    so = self.portal.sale_opportunity_module.newContent(
                              portal_type='Sale Opportunity')
301
    self.assertEqual('draft', so.getSimulationState())
302
    self.portal.portal_workflow.doActionFor(so, 'submit_action')
303
    self.assertEqual('submitted', so.getSimulationState())
304
    self.portal.portal_workflow.doActionFor(so, 'validate_action')
305
    self.assertEqual('contacted', so.getSimulationState())
306
    self.portal.portal_workflow.doActionFor(so, 'enquire_action')
307
    self.assertEqual('enquired', so.getSimulationState())
308
    self.portal.portal_workflow.doActionFor(so, 'offer_action')
309
    self.assertEqual('offered', so.getSimulationState())
310
    self.portal.portal_workflow.doActionFor(so, 'sell_action')
311
    self.assertEqual('sold', so.getSimulationState())
312 313 314 315 316 317 318

  def test_SaleOpportunityRejected(self):
    # test the workflow of sale opportunities, when the sale opportunity is
    # finaly rejected.
    # Uses different transitions than test_SaleOpportunitySold
    so = self.portal.sale_opportunity_module.newContent(
                              portal_type='Sale Opportunity')
319
    self.assertEqual('draft', so.getSimulationState())
320
    self.portal.portal_workflow.doActionFor(so, 'validate_action')
321
    self.assertEqual('contacted', so.getSimulationState())
322
    self.portal.portal_workflow.doActionFor(so, 'enquire_action')
323
    self.assertEqual('enquired', so.getSimulationState())
324
    self.portal.portal_workflow.doActionFor(so, 'offer_action')
325
    self.assertEqual('offered', so.getSimulationState())
326
    self.portal.portal_workflow.doActionFor(so, 'reject_action')
327
    self.assertEqual('rejected', so.getSimulationState())
328 329 330 331 332 333

  def test_SaleOpportunityExpired(self):
    # test the workflow of sale opportunities, when the sale opportunity
    # expires
    so = self.portal.sale_opportunity_module.newContent(
                              portal_type='Sale Opportunity')
334
    self.assertEqual('draft', so.getSimulationState())
335
    self.portal.portal_workflow.doActionFor(so, 'validate_action')
336
    self.assertEqual('contacted', so.getSimulationState())
337
    self.portal.portal_workflow.doActionFor(so, 'expire_action')
338
    self.assertEqual('expired', so.getSimulationState())
339

340
  @expectedFailure
341 342 343
  def test_Event_AcknowledgeAndCreateEvent(self):
    """
    Make sure that when acknowledge event, we can create a new event.
344 345

    XXX This is probably meaningless in near future. event_workflow
346 347
    will be reviewed in order to have steps closer to usual packing
    list workflow. For now we have a conflict name between the
348 349 350
    acknowledge method of event_workflow and Acknowledgement features
    that comes with AcknowledgementTool. So for now disable site
    message in this test.
351 352 353
    """
    portal_workflow = self.portal.portal_workflow

354
    event_type_list = [x for x in self.portal.getPortalEventTypeList() \
355
                       if x not in  ['Site Message', 'Acknowledgement']]
356

357
    # if create_event option is false, it does not create a new event.
358
    for portal_type in event_type_list:
359 360 361 362 363 364 365 366 367 368 369
      ticket = self.portal.meeting_module.newContent(portal_type='Meeting',
                                                     title='Meeting1')
      ticket_url = ticket.getRelativeUrl()
      event = self.portal.event_module.newContent(portal_type=portal_type,
                                                  follow_up=ticket_url)
      self.tic()
      self.assertEqual(len(event.getCausalityRelatedValueList()), 0)
      event.receive()
      portal_workflow.doActionFor(event, 'acknowledge_action', create_event=0)
      self.tic()
      self.assertEqual(len(event.getCausalityRelatedValueList()), 0)
Nicolas Delaby's avatar
Nicolas Delaby committed
370

371
    # if create_event option is true, it create a new event.
372
    for portal_type in event_type_list:
373 374 375 376 377 378 379 380 381 382 383 384 385 386
      ticket = self.portal.meeting_module.newContent(portal_type='Meeting',
                                                     title='Meeting1')
      ticket_url = ticket.getRelativeUrl()
      event = self.portal.event_module.newContent(portal_type=portal_type,
                                                  follow_up=ticket_url)
      self.tic()
      self.assertEqual(len(event.getCausalityRelatedValueList()), 0)
      event.receive()
      portal_workflow.doActionFor(event, 'acknowledge_action', create_event=1)
      self.tic()
      self.assertEqual(len(event.getCausalityRelatedValueList()), 1)
      new_event = event.getCausalityRelatedValue()
      self.assertEqual(new_event.getFollowUp(), ticket_url)

387 388
    # if quote_original_message option is true, the new event content will be
    # the current event message quoted.
389
    for portal_type in event_type_list:
390 391 392 393 394 395 396
      ticket = self.portal.meeting_module.newContent(portal_type='Meeting',
                                                     title='Meeting1')
      ticket_url = ticket.getRelativeUrl()
      event = self.portal.event_module.newContent(portal_type=portal_type,
                                                  follow_up=ticket_url,
                                                  title='Event Title',
                                                  text_content='Event Content',
397
                                                  content_type='text/plain')
398 399 400 401 402 403 404 405 406 407
      self.tic()
      self.assertEqual(len(event.getCausalityRelatedValueList()), 0)
      event.receive()
      portal_workflow.doActionFor(event, 'acknowledge_action',
                                  create_event=1,
                                  quote_original_message=1)
      self.tic()
      self.assertEqual(len(event.getCausalityRelatedValueList()), 1)
      new_event = event.getCausalityRelatedValue()
      self.assertEqual(new_event.getFollowUp(), ticket_url)
408
      self.assertEqual(new_event.getContentType(), 'text/plain')
409 410 411
      self.assertEqual(new_event.getTextContent(), '> Event Content')
      self.assertEqual(new_event.getTitle(), 'Re: Event Title')

412 413
  def test_SupportRequest_referenceAutomaticallyGenerated(self):
    """
414
      When you create or clone a Support Request document, it must
415 416 417 418 419 420 421 422
      have the reference generated automatically.
    """
    portal_type = "Support Request"
    title = "Title of the Support Request"
    content = "This is the content of the Support Request"
    module = self.portal.support_request_module
    support_request = module.newContent(portal_type=portal_type,
                                        title=title,)
423
    self.tic()
424 425 426 427 428

    self.assertNotEquals(None, support_request.getReference())

    new_support_request = support_request.Base_createCloneDocument(
                                                                 batch_mode=1)
429
    self.assertEqual(new_support_request.getTitle(), title)
430
    self.assertNotEquals(None, support_request.getReference())
431
    self.assertNotEquals(support_request.getReference(),
432 433 434
                                        new_support_request.getReference())


435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
  def test_Event_getResourceItemList(self):
    """Event_getResourceItemList returns
    category item list with base category in path, just
    like resource.getCategoryChildItemList(base=True) does.
    This is not the expected behaviour because it
    duplicates the base_category in categories_list:
      - resource/resource/my_category_id
    This test checks that relative_url return
    by Event_getResourceItemList are consistent.
    Check also the support of backward compatibility to not break UI
    if resource is already defined with base_category
    in its relative_url value.
    """
    # create resource categories.
    resource = self.portal.portal_categories.resource
    for i in range(3):
      resource.newContent(portal_type='Category',
                          title='Title%s' % i,
                          id=i)
    # create a person like a resource to declare it as a resource
    person = self.portal.person_module.newContent(portal_type='Person')
    resource_list = [category.getRelativeUrl() \
                                      for category in resource.contentValues()]
    resource_list.append(person.getRelativeUrl())
459
    # XXX this preference is obsolete, this is now based on use category
460
    system_preference = self.getDefaultSystemPreference()
461 462 463 464 465
    system_preference.setPreferredEventResourceList(resource_list)
    self.tic()
    # Then create One event and play with it
    portal_type = 'Visit'
    module = self.portal.getDefaultModule(portal_type)
466 467 468 469 470 471
    event = module.newContent(portal_type=portal_type)
    # Check that existing valid resource relations which should not be normaly
    # found by Event_getResourceItemList are present.
    self.assertTrue(event.getResource() not in\
                       [item[1] for item in event.Event_getResourceItemList()])
    event.setResource('0')
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
    self.assertTrue(event.getResourceValue() is not None)
    self.assertTrue(event.getResource() in\
                       [item[1] for item in event.Event_getResourceItemList()])
    # Check Backward compatibility support
    # When base_category value is stored in categories_list
    # resource/resource/my_category_id instead of resource/my_category_id
    event.setResource('resource/0')
    self.assertTrue(event.getResourceValue() is not None)
    self.assertTrue(event.getResource() in\
                       [item[1] for item in event.Event_getResourceItemList()])

    # Check that relation with an object which
    # is not a Category works.
    event.setResourceValue(person)
    self.assertTrue(event.getResource() in\
                       [item[1] for item in event.Event_getResourceItemList()])
488

489 490 491 492 493 494 495 496
  def test_EventPath(self):
    """
      Check that configuring the Event Path on Campaign, all events are
      created according to the domain selected
    """
    mapping_method_id = "NotificationMessage_getSubstitutionMappingDictFromEvent"
    portal = self.portal
    notification_message_reference = 'campaign-Event.Path'
497
    service = portal.service_module.newContent(portal_type='Service')
498 499 500 501
    resource = portal.notification_message_module.newContent(
        reference=notification_message_reference,
        content_type="text/html",
        portal_type="Notification Message",
502
        specialise_value=service,
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
        text_content_substitution_mapping_method_id=mapping_method_id,
        text_content="Hello ${destination_title}")
    resource.validate()
    sender = portal.person_module.newContent(portal_type="Person",
        reference='sender', first_name='Sender')
    first_user = portal.person_module.newContent(portal_type="Person",
        reference='validated_user', first_name="First User")
    first_user.validate()
    organisation = portal.organisation_module.newContent(portal_type="Organisation",
        title="Dummy SA")
    organisation.validate()
    base_domain = portal.portal_domains.newContent(portal_type="Base Domain",
      id='event_path_domain')
    person_domain = base_domain.newContent(portal_type="Domain",
      title="All Customers")
518
    person_domain.setCriterionPropertyList(['portal_type', 'validation_state'])
519
    person_domain.setCriterion('portal_type', identity=['Person'])
520
    person_domain.setCriterion('validation_state', identity=['validated'])
521 522 523 524 525 526 527 528 529 530 531 532 533
    organisation_domain = base_domain.newContent(portal_type="Domain",
      title="All Organisations")
    organisation_domain.setCriterionPropertyList(['portal_type'])
    organisation_domain.setCriterion('portal_type', identity=['Organisation'])

    campaign = self.portal.campaign_module.newContent(portal_type="Campaign",
        default_event_path_event_portal_type="Mail Message",
        default_event_path_destination="portal_domains/%s" % person_domain.getRelativeUrl(),
        default_event_path_source=sender.getRelativeUrl(),
        default_event_path_resource=resource.getRelativeUrl())
    self.tic()
    campaign.Ticket_createEventFromDefaultEventPath()
    self.tic()
534 535
    event_list = [event for event in campaign.getFollowUpRelatedValueList()
      if event.getPortalType() != 'Mail Message']
536
    self.assertEqual(event_list, [])
537 538 539
    event_list = campaign.getFollowUpRelatedValueList(portal_type='Mail Message')
    self.assertNotEquals(event_list, [])
    destination_list = map(lambda x: x.getDestinationValue(), event_list)
540
    self.assertEqual(destination_list, [first_user])
541
    mail_message = event_list[0]
542
    self.assertEqual(sender.getRelativeUrl(), mail_message.getSource())
543
    self.assertEqual(mail_message.getContentType(), "text/html")
544 545
    self.assertEqual(mail_message.getTextContent(), "Hello %s" % first_user.getTitle())
    self.assertEqual(mail_message.getResourceValue(), service)
546 547 548 549 550 551 552 553 554

    campaign = portal.campaign_module.newContent(portal_type="Campaign",
        default_event_path_event_portal_type="Visit",
        default_event_path_destination='portal_domains/%s' % organisation_domain.getRelativeUrl(),
        default_event_path_source=sender.getRelativeUrl(),
        default_event_path_resource=resource.getRelativeUrl())
    self.tic()
    campaign.Ticket_createEventFromDefaultEventPath()
    self.tic()
555 556
    event_list = [event for event in campaign.getFollowUpRelatedValueList()
      if event.getPortalType() != 'Visit']
557
    self.assertEqual([], event_list)
558 559 560
    event_list = campaign.getFollowUpRelatedValueList(portal_type='Visit')
    self.assertNotEquals([], event_list)
    destination_uid_list = map(lambda x: x.getDestinationUid(), event_list)
561
    self.assertEqual([organisation.getUid()], destination_uid_list)
562

563
    resource_value_list = map(lambda x: x.getResourceValue(), event_list)
564
    self.assertEqual([service], resource_value_list)
565

566 567 568 569 570 571 572 573 574 575 576
  def test_OutcomePath(self):
    service = self.portal.service_module.newContent(portal_type='Service')
    currency = self.portal.currency_module.newContent(portal_type='Currency')

    campaign = self.portal.campaign_module.newContent(portal_type="Campaign")
    campaign.setDefaultOutcomePathQuantity(3)
    campaign.setDefaultOutcomePathQuantityUnit('unit/piece')
    campaign.setDefaultOutcomePathResourceValue(service)
    campaign.setDefaultOutcomePathPrice(4)
    campaign.setDefaultOutcomePathPriceCurrency(currency.getRelativeUrl())

577
    self.assertEqual(3*4, campaign.getDefaultOutcomePathTotalPrice())
578

579 580 581
    self.assertEqual(3, campaign.getDefaultOutcomePathQuantity())
    self.assertEqual('unit/piece', campaign.getDefaultOutcomePathQuantityUnit())
    self.assertEqual(service.getRelativeUrl(),
582
      campaign.getDefaultOutcomePathResource())
583 584
    self.assertEqual(4, campaign.getDefaultOutcomePathPrice())
    self.assertEqual(currency.getRelativeUrl(),
585 586 587
      campaign.getDefaultOutcomePathPriceCurrency())

    outcome_path = campaign._getOb('default_outcome_path')
588
    self.assertEqual('Outcome Path', outcome_path.getPortalType())
589

590
class TestCRMMailIngestion(BaseTestCRM):
Yusei Tahara's avatar
Yusei Tahara committed
591
  """Test Mail Ingestion for standalone CRM.
592
  """
593 594
  def getTitle(self):
    return "CRM Mail Ingestion"
595 596

  def getBusinessTemplateList(self):
597
    # Mail Ingestion must work with CRM alone.
598
    return ('erp5_core_proxy_field_legacy',
599
            'erp5_full_text_mroonga_catalog',
600
            'erp5_base',
601
            'erp5_ingestion',
Yusei Tahara's avatar
Yusei Tahara committed
602
            'erp5_ingestion_mysql_innodb_catalog',
Jérome Perrin's avatar
Jérome Perrin committed
603
            'erp5_pdm',
Yusei Tahara's avatar
Yusei Tahara committed
604
            'erp5_crm',
605
            )
606 607

  def afterSetUp(self):
608
    super(TestCRMMailIngestion, self).afterSetUp()
609
    portal = self.portal
610

611
    # create customer organisation and person
612 613 614 615
    portal.organisation_module.newContent(
            id='customer',
            portal_type='Organisation',
            title='Customer')
616
    customer_organisation = portal.organisation_module.customer
617 618 619 620 621
    portal.person_module.newContent(
            id='sender',
            title='Sender',
            subordination_value=customer_organisation,
            default_email_text='sender@customer.com')
622
    # also create the recipients
623 624 625 626 627 628 629 630
    portal.person_module.newContent(
            id='me',
            title='Me',
            default_email_text='me@erp5.org')
    portal.person_module.newContent(
            id='he',
            title='He',
            default_email_text='he@erp5.org')
631

632 633
    # make sure customers are available to catalog
    self.tic()
634

Yusei Tahara's avatar
Yusei Tahara committed
635 636 637 638 639 640
  def _readTestData(self, filename):
    """read test data from data directory."""
    return file(os.path.join(os.path.dirname(__file__),
                             'test_data', 'crm_emails', filename)).read()

  def _ingestMail(self, filename=None, data=None):
641
    """ingest an email from the mail in data dir named `filename`"""
Yusei Tahara's avatar
Yusei Tahara committed
642 643
    if data is None:
      data=self._readTestData(filename)
644 645
    return self.portal.portal_contributions.newContent(
                    container_path='event_module',
Nicolas Delaby's avatar
Nicolas Delaby committed
646
                    filename='postfix_mail.eml',
647 648 649 650
                    data=data)

  def test_findTypeByName_MailMessage(self):
    # without this, ingestion will not work
651
    self.assertEqual(
652 653
      'Mail Message',
      self.portal.portal_contribution_registry.findPortalTypeName(
Nicolas Delaby's avatar
Nicolas Delaby committed
654
      filename='postfix_mail.eml', content_type='message/rfc822', data='Test'
655
      ))
656

657 658 659 660 661 662 663 664 665 666 667
  def test_Base_getEntityListFromFromHeader(self):
    expected_values = (
      ('me@erp5.org', ['person_module/me']),
      ('me@erp5.org, he@erp5.org', ['person_module/me', 'person_module/he']),
      ('Sender <sender@customer.com>', ['person_module/sender']),
      # tricks to confuse the e-mail parser:
      # a comma in the name
      ('"Sender," <sender@customer.com>, he@erp5.org', ['person_module/sender',
                                                        'person_module/he']),
      # multiple e-mails in the "Name" part that shouldn't be parsed
      ('"me@erp5.org,sender@customer.com," <he@erp5.org>', ['person_module/he']),
668 669
      # capitalised version
      ('"me@erp5.org,sEnder@CUSTOMER.cOm," <he@ERP5.OrG>', ['person_module/he']),
670 671 672 673 674 675 676 677
      # a < sign
      ('"He<" <he@erp5.org>', ['person_module/he']),
    )
    portal = self.portal
    Base_getEntityListFromFromHeader = portal.Base_getEntityListFromFromHeader
    pc = self.portal.portal_catalog
    for header, expected_paths in expected_values:
      paths = [entity.getRelativeUrl()
678
               for entity in portal.Base_getEntityListFromFromHeader(header)]
679
      self.assertEqual(paths, expected_paths,
680 681 682
                        '%r should return %r, but returned %r' %
                        (header, expected_paths, paths))

683 684 685
  def test_document_creation(self):
    # CRM email ingestion creates a Mail Message in event_module
    event = self._ingestMail('simple')
686 687 688 689 690
    self.assertEqual(len(self.portal.event_module), 1)
    self.assertEqual(event, self.portal.event_module.contentValues()[0])
    self.assertEqual('Mail Message', event.getPortalType())
    self.assertEqual('text/plain', event.getContentType())
    self.assertEqual('message/rfc822', event._baseGetContentType())
691 692 693 694
    # check if parsing of metadata from content is working
    content_dict = {'source_list': ['person_module/sender'],
                    'destination_list': ['person_module/me',
                                         'person_module/he']}
695
    self.assertEqual(event.getPropertyDictFromContent(), content_dict)
696

697
  def test_title(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
698
    # title is found automatically, based on the Subject: header in the mail
699
    event = self._ingestMail('simple')
700 701
    self.assertEqual('Simple Mail Test', event.getTitle())
    self.assertEqual('Simple Mail Test', event.getTitleOrId())
702 703 704 705

  def test_asText(self):
    # asText requires portal_transforms
    event = self._ingestMail('simple')
706
    self.assertEqual('Hello,\nContent of the mail.\n', str(event.asText()))
707

708 709 710 711 712
  def test_sender(self):
    # source is found automatically, based on the From: header in the mail
    event = self._ingestMail('simple')
    # metadata discovery is done in an activity
    self.tic()
713
    self.assertEqual('person_module/sender', event.getSource())
714 715 716 717 718

  def test_recipient(self):
    # destination is found automatically, based on the To: header in the mail
    event = self._ingestMail('simple')
    self.tic()
719 720
    destination_list = event.getDestinationList()
    destination_list.sort()
721
    self.assertEqual(['person_module/he', 'person_module/me'],
722
                      destination_list)
723

724 725 726 727
  def test_clone(self):
    # cloning an event must keep title and text-content
    event = self._ingestMail('simple')
    self.tic()
728 729 730 731 732 733 734
    self.assertEqual('Simple Mail Test', event.getTitle())
    self.assertEqual('Simple Mail Test', event.getTitleOrId())
    self.assertEqual('Hello,\nContent of the mail.\n', str(event.asText()))
    self.assertEqual('Hello,\nContent of the mail.\n', str(event.getTextContent()))
    self.assertEqual('Mail Message', event.getPortalType())
    self.assertEqual('text/plain', event.getContentType())
    self.assertEqual('message/rfc822', event._baseGetContentType())
735 736 737 738
    # check if parsing of metadata from content is working
    content_dict = {'source_list': ['person_module/sender'],
                    'destination_list': ['person_module/me',
                                         'person_module/he']}
739
    self.assertEqual(event.getPropertyDictFromContent(), content_dict)
740 741
    new_event = event.Base_createCloneDocument(batch_mode=1)
    self.tic()
742 743 744 745 746 747
    self.assertEqual('Simple Mail Test', new_event.getTitle())
    self.assertEqual('Simple Mail Test', new_event.getTitleOrId())
    self.assertEqual('Hello,\nContent of the mail.\n', str(new_event.asText()))
    self.assertEqual('Hello,\nContent of the mail.\n', str(new_event.getTextContent()))
    self.assertEqual('Mail Message', new_event.getPortalType())
    self.assertEqual('text/plain', new_event.getContentType())
748

749
    # check that metadatas read from data are copied on cloned event
750 751
    self.assertEqual(new_event.getSourceList(), ['person_module/sender'])
    self.assertEqual(new_event.getDestinationList(), ['person_module/me',
752
                                                       'person_module/he'])
753

754 755 756
    # cloned event got a new reference
    self.assertNotEqual(new_event.getReference(), event.getReference())

757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
  def test_getPropertyDictFromContent_and_defined_arrow(self):
    # If source/destination are set on event, then getPropertyDictFromContent
    # should not lookup one based on email address.
    person = self.portal.person_module.newContent(
        portal_type='Person',
        default_email_coordinate_text='destination@example.com',)
    organisation = self.portal.organisation_module.newContent(
        portal_type='Organisation',
        default_email_coordinate_text='destination@example.com',)
    source_person = self.portal.person_module.newContent(
        portal_type='Person',
        default_email_coordinate_text='source@example.com',)
    self.tic()
    event = self.portal.event_module.newContent(
        portal_type='Mail Message',
        destination_value=organisation,
        data='\r\n'.join(textwrap.dedent('''
        From: Source <source@example.com>
        To: destination <destination@example.com>
        Subject: mail subject

        content
        ''').splitlines()[1:]))

    property_dict = event.getPropertyDictFromContent()
    # destination is set on the event. In this case it is kept as is.
783
    self.assertEqual([organisation.getRelativeUrl()],
784 785 786
        property_dict['destination_list'])
    # source is not set. In this case it is searched in catalog based on email
    # address
787
    self.assertEqual([source_person.getRelativeUrl()],
788 789 790
        property_dict['source_list'])


791 792 793 794 795 796 797 798 799
  def test_follow_up(self):
    # follow up is found automatically, based on the content of the mail, and
    # what you defined in preference regexpr.
    # But, we don't want it to associate with the first campaign simply
    # because we searched against nothing
    self.portal.campaign_module.newContent(portal_type='Campaign')
    self.tic()
    event = self._ingestMail('simple')
    self.tic()
800
    self.assertEqual(None, event.getFollowUp())
Yusei Tahara's avatar
Yusei Tahara committed
801 802 803 804 805 806

  def test_portal_type_determination(self):
    """
    Make sure that ingested email will be correctly converted to
    appropriate portal type by email metadata.
    """
807 808 809 810 811 812
    def getLastCreatedEvent(module):
      object_list = module.contentValues()
      object_list.sort(key=lambda e:e.getCreationDate())
      return object_list[-1]

    portal = self.portal
813
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
814 815
    message.replace_header('subject', 'Visit:Company A')
    data = message.as_string()
816 817 818
    self._ingestMail(data=data)
    self.tic()
    document = getLastCreatedEvent(portal.event_module)
Nicolas Delaby's avatar
Nicolas Delaby committed
819
    self.assertEqual(document.getPortalType(), 'Visit')
Yusei Tahara's avatar
Yusei Tahara committed
820

821
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
822 823
    message.replace_header('subject', 'Fax:Company B')
    data = message.as_string()
824 825 826
    self._ingestMail(data=data)
    self.tic()
    document = getLastCreatedEvent(portal.event_module)
Nicolas Delaby's avatar
Nicolas Delaby committed
827
    self.assertEqual(document.getPortalType(), 'Fax Message')
Yusei Tahara's avatar
Yusei Tahara committed
828

829
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
830 831
    message.replace_header('subject', 'TEST:Company B')
    data = message.as_string()
832 833 834
    self._ingestMail(data=data)
    self.tic()
    document = getLastCreatedEvent(portal.event_module)
Nicolas Delaby's avatar
Nicolas Delaby committed
835
    self.assertEqual(document.getPortalType(), 'Mail Message')
Yusei Tahara's avatar
Yusei Tahara committed
836

837
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
838 839
    message.replace_header('subject', 'visit:Company A')
    data = message.as_string()
840 841 842
    self._ingestMail(data=data)
    self.tic()
    document = getLastCreatedEvent(portal.event_module)
Nicolas Delaby's avatar
Nicolas Delaby committed
843
    self.assertEqual(document.getPortalType(), 'Visit')
Yusei Tahara's avatar
Yusei Tahara committed
844

845
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
846 847
    message.replace_header('subject', 'phone:Company B')
    data = message.as_string()
848 849 850
    self._ingestMail(data=data)
    self.tic()
    document = portal.event_module[portal.event_module.objectIds()[-1]]
Nicolas Delaby's avatar
Nicolas Delaby committed
851
    self.assertEqual(document.getPortalType(), 'Phone Call')
Yusei Tahara's avatar
Yusei Tahara committed
852

853
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
854 855
    message.replace_header('subject', 'LETTER:Company C')
    data = message.as_string()
856 857 858
    self._ingestMail(data=data)
    self.tic()
    document = getLastCreatedEvent(portal.event_module)
Nicolas Delaby's avatar
Nicolas Delaby committed
859
    self.assertEqual(document.getPortalType(), 'Letter')
Yusei Tahara's avatar
Yusei Tahara committed
860

861
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
862 863 864
    body = message.get_payload()
    message.set_payload('Visit:%s' % body)
    data = message.as_string()
865 866 867
    self._ingestMail(data=data)
    self.tic()
    document = getLastCreatedEvent(portal.event_module)
Nicolas Delaby's avatar
Nicolas Delaby committed
868
    self.assertEqual(document.getPortalType(), 'Visit')
Yusei Tahara's avatar
Yusei Tahara committed
869

870
    message = message_from_string(self._readTestData('simple'))
Yusei Tahara's avatar
Yusei Tahara committed
871 872 873
    body = message.get_payload()
    message.set_payload('PHONE CALL:%s' % body)
    data = message.as_string()
874 875 876
    self._ingestMail(data=data)
    self.tic()
    document = getLastCreatedEvent(portal.event_module)
Nicolas Delaby's avatar
Nicolas Delaby committed
877
    self.assertEqual(document.getPortalType(), 'Phone Call')
Yusei Tahara's avatar
Yusei Tahara committed
878

Yusei Tahara's avatar
Yusei Tahara committed
879 880 881 882 883 884 885 886 887 888 889 890 891 892
  def test_forwarder_mail(self):
    """
    Make sure that if ingested email is forwarded one, the sender of
    original mail should be the sender of event and the sender of
    forwarded mail should be the recipient of event.
    """
    document = self._ingestMail(filename='forwarded')

    self.tic()

    self.assertEqual(document.getContentInformation().get('From'), 'Me <me@erp5.org>')
    self.assertEqual(document.getContentInformation().get('To'), 'crm@erp5.org')
    self.assertEqual(document.getSourceValue().getTitle(), 'Sender')
    self.assertEqual(document.getDestinationValue().getTitle(), 'Me')
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907

  def test_forwarder_mail_with_attachment(self):
    """
    Make sure that if ingested email is forwarded one, the sender of
    original mail should be the sender of event and the sender of
    forwarded mail should be the recipient of event.
    """
    document = self._ingestMail(filename='forwarded_attached')

    self.tic()

    self.assertEqual(document.getContentInformation().get('From'), 'Me <me@erp5.org>')
    self.assertEqual(document.getContentInformation().get('To'), 'crm@erp5.org')
    self.assertEqual(document.getSourceValue().getTitle(), 'Sender')
    self.assertEqual(document.getDestinationValue().getTitle(), 'Me')
Yusei Tahara's avatar
Yusei Tahara committed
908

909 910 911 912 913 914 915 916 917 918 919 920 921 922
  def test_encoding(self):
    document = self._ingestMail(filename='encoded')

    self.tic()

    self.assertEqual(document.getContentInformation().get('To'),
                     'Me <me@erp5.org>')
    self.assertEqual(document.getSourceValue().getTitle(), 'Sender')
    self.assertEqual(document.getDestinationValue().getTitle(), 'Me')
    self.assertEqual(document.getContentInformation().get('Subject'),
                     'Test éncödèd email')
    self.assertEqual(document.getTitle(), 'Test éncödèd email')
    self.assertEqual(document.getTextContent(), 'cöntént\n')

Yusei Tahara's avatar
Yusei Tahara committed
923

924 925 926 927 928 929 930 931 932 933 934
  def test_HTML_multipart_attachments(self):
    """Test that html attachments are cleaned up.
    and check the behaviour of getTextContent
    if multipart/alternative return html
    if multipart/mixed return text
    """
    document = self._ingestMail(filename='sample_multipart_mixed_and_alternative')
    self.tic()
    stripped_html = document.asStrippedHTML()
    self.assertTrue('<form' not in stripped_html)
    self.assertTrue('<form' not in document.getAttachmentData(4))
935
    self.assertEqual('This is my content.\n*ERP5* is a Free _Software_\n',
936
                      document.getAttachmentData(2))
937 938
    self.assertEqual('text/html', document.getContentType())
    self.assertEqual('\n<html>\n<head>\n\n<meta http-equiv="content-type"'\
939 940 941 942 943
                      ' content="text/html; charset=utf-8" />\n'\
                      '</head>\n<body text="#000000"'\
                      ' bgcolor="#ffffff">\nThis is my content.<br />\n'\
                      '<b>ERP5</b> is a Free <u>Software</u><br />'\
                      '\n\n</body>\n</html>\n', document.getAttachmentData(3))
944
    self.assertEqual(document.getAttachmentData(3), document.getTextContent())
945 946 947 948

    # now check a message with multipart/mixed
    mixed_document = self._ingestMail(filename='sample_html_attachment')
    self.tic()
949
    self.assertEqual(mixed_document.getAttachmentData(1),
950
                      mixed_document.getTextContent())
951
    self.assertEqual('Hi, this is the Message.\nERP5 is a free software.\n\n',
952
                      mixed_document.getTextContent())
953
    self.assertEqual('text/plain', mixed_document.getContentType())
954

955 956 957 958 959 960 961 962 963 964 965 966 967
  def test_flawed_html_attachment(self):
    portal_type = 'Mail Message'
    event = self.portal.getDefaultModule(portal_type).newContent(portal_type=portal_type)
    # build message content with flwd attachment
    plain_text_message = 'You can read this'
    html_filename = 'broken_html.html'
    file_path = '%s/test_data/%s' % (__file__.rstrip('c').replace(__name__+'.py', ''),
                                     html_filename,)
    html_message = open(file_path, 'r').read()
    message = MIMEMultipart('alternative')
    message.attach(MIMEText('text plain content', _charset='utf-8'))
    part = MIMEBase('text', 'html')
    part.set_payload(html_message)
968
    encoders.encode_base64(part)
969 970 971 972 973 974 975 976 977

    part.add_header('Content-Disposition', 'attachment',
                    filename=html_filename)
    part.add_header('Content-ID', '<%s>' % \
                    ''.join(['%s' % ord(i) for i in html_filename]))
    message.attach(part)
    event.setData(message.as_string())
    self.tic()
    self.assertTrue('html' in event.getTextContent())
978
    self.assertEqual(len(event.getAttachmentInformationList()), 2)
979 980 981 982 983 984 985
    self.assertTrue(bool(event.getAttachmentData(1)))
    self.assertTrue(bool(event.getAttachmentData(2)))





986

987 988 989 990 991
## TODO:
##
##  def test_attachements(self):
##    event = self._ingestMail('with_attachements')
##
992

993
class TestCRMMailSend(BaseTestCRM):
Yusei Tahara's avatar
Yusei Tahara committed
994 995
  """Test Mail Sending for CRM
  """
996 997
  def getTitle(self):
    return "CRM Mail Sending"
Yusei Tahara's avatar
Yusei Tahara committed
998 999

  def getBusinessTemplateList(self):
Yusei Tahara's avatar
Yusei Tahara committed
1000 1001
    # In this test, We will attach some document portal types in event.
    # So we add DMS and Web.
1002 1003 1004 1005 1006 1007 1008
    return ('erp5_base',
            'erp5_ingestion',
            'erp5_ingestion_mysql_innodb_catalog',
            'erp5_crm',
            'erp5_web',
            'erp5_dms',
            )
Yusei Tahara's avatar
Yusei Tahara committed
1009 1010

  def afterSetUp(self):
1011
    super(TestCRMMailSend, self).afterSetUp()
Yusei Tahara's avatar
Yusei Tahara committed
1012 1013
    portal = self.portal
    # create customer organisation and person
1014 1015 1016 1017
    portal.organisation_module.newContent(
            id='customer',
            portal_type='Organisation',
            title='Customer')
1018
    customer_organisation = portal.organisation_module.customer
1019 1020 1021 1022 1023 1024 1025
    portal.person_module.newContent(
            id='recipient',
            # The ',' below is to force quoting of the name in e-mail
            # addresses on Zope 2.12
            title='Recipient,',
            subordination_value=customer_organisation,
            default_email_text='recipient@example.com')
1026 1027 1028 1029 1030 1031 1032
    portal.person_module.newContent(
            id='non_ascii_recipient',
            # The ',' below is to force quoting of the name in e-mail
            # addresses on Zope 2.12
            title='Recipient, 🐈 fan',
            subordination_value=customer_organisation,
            default_email_text='recipient@example.com')
1033 1034 1035 1036 1037 1038 1039
    # also create the sender
    portal.person_module.newContent(
            id='me',
            # The ',' below is to force quoting of the name in e-mail
            # addresses on Zope 2.12
            title='Me,',
            default_email_text='me@erp5.org')
1040 1041 1042 1043 1044 1045
    portal.person_module.newContent(
            id='non_ascii_me',
            # The ',' below is to force quoting of the name in e-mail
            # addresses on Zope 2.12
            title='Me, 🐈 fan',
            default_email_text='me@erp5.org')
Yusei Tahara's avatar
Yusei Tahara committed
1046 1047

    # set preference
1048 1049 1050
    pref = self.getDefaultSystemPreference()
    pref.setPreferredDocumentFilenameRegularExpression(FILENAME_REGULAR_EXPRESSION)
    pref.setPreferredDocumentReferenceRegularExpression(REFERENCE_REGULAR_EXPRESSION)
Yusei Tahara's avatar
Yusei Tahara committed
1051 1052 1053 1054

    # make sure customers are available to catalog
    self.tic()

1055 1056 1057
  def test_MailFromMailMessageEvent(self):
    # passing start_action transition on event workflow will send an email to the
    # person as destination
1058
    text_content = 'Mail Content'
1059 1060 1061 1062
    event = self.portal.event_module.newContent(portal_type='Mail Message')
    event.setSource('person_module/me')
    event.setDestination('person_module/recipient')
    event.setTitle('A Mail')
1063
    event.setTextContent(text_content)
1064
    self.portal.portal_workflow.doActionFor(event, 'start_action')
1065
    self.tic()
1066
    last_message, = self.portal.MailHost._message_list
1067 1068
    self.assertNotEquals((), last_message)
    mfrom, mto, messageText = last_message
1069 1070 1071
    self.assertEqual('"Me," <me@erp5.org>', mfrom)
    self.assertEqual(['"Recipient," <recipient@example.com>'], mto)
    self.assertEqual(event.getTextContent(), text_content)
1072
    message = message_from_string(messageText)
1073

1074
    self.assertEqual('A Mail', decode_header(message['Subject'])[0][0])
1075 1076 1077 1078
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
1079
    self.assertEqual(text_content, part.get_payload(decode=True))
1080

1081 1082 1083 1084 1085 1086 1087 1088
    #
    # Test multiple recipients.
    #
    event = self.portal.event_module.newContent(portal_type='Mail Message')
    event.setSource('person_module/me')
    # multiple recipients.
    event.setDestinationList(['person_module/recipient', 'person_module/me'])
    event.setTitle('A Mail')
1089
    event.setTextContent(text_content)
1090
    self.portal.portal_workflow.doActionFor(event, 'start_action')
1091 1092 1093 1094
    self.tic()
    last_message_1, last_message_2 = self.portal.MailHost._message_list[-2:]
    self.assertNotEquals((), last_message_1)
    self.assertNotEquals((), last_message_2)
1095 1096
    # check last message 1 and last message 2 (the order is random)
    # both should have 'From: Me'
1097
    self.assertEqual(['"Me," <me@erp5.org>', '"Me," <me@erp5.org>'],
1098 1099
                      [x[0] for x in (last_message_1, last_message_2)])
    # one should have 'To: Me' and the other should have 'To: Recipient'
1100
    self.assertEqual([['"Me," <me@erp5.org>'], ['"Recipient," <recipient@example.com>']],
1101
                      sorted([x[1] for x in (last_message_1, last_message_2)]))
1102

1103
  def test_MailFromMailMessageEventNoSendMail(self):
1104 1105 1106
    # for Mail Message, passing start_action transition on event workflow will send an email to the
    # person as destination. To prevent this, one can use initial_stop_action to mark
    # the event receieved.
1107 1108 1109 1110 1111
    event = self.portal.event_module.newContent(portal_type='Mail Message')
    event.setSource('person_module/me')
    event.setDestination('person_module/recipient')
    event.setTitle('A Mail')
    event.setTextContent('Mail Content')
1112
    self.portal.portal_workflow.doActionFor(event, 'initial_stop_action')
1113
    self.assertEqual('stopped', event.getSimulationState())
1114 1115 1116
    self.tic()
    # no mail sent
    last_message = self.portal.MailHost._last_message
1117
    self.assertEqual((), last_message)
1118 1119 1120 1121 1122

  def test_MailFromOtherEvents(self):
    # passing start_action transition on event workflow will not send an email
    # when the portal type is not Mail Message
    for ptype in [t for t in self.portal.getPortalEventTypeList()
1123 1124
        if t not in ('Mail Message', 'Document Ingestion Message',
          'Acknowledgement')]:
1125 1126 1127 1128
      event = self.portal.event_module.newContent(portal_type=ptype)
      event.setSource('person_module/me')
      event.setDestination('person_module/recipient')
      event.setTextContent('Hello !')
1129
      self.portal.portal_workflow.doActionFor(event, 'start_action')
Rafael Monnerat's avatar
Rafael Monnerat committed
1130 1131 1132

      self.tic()
      # this means no message have been set
1133 1134
      self.assertEqual([], self.portal.MailHost._message_list)
      self.assertEqual((), self.portal.MailHost._last_message)
Rafael Monnerat's avatar
Rafael Monnerat committed
1135

1136
  def test_MailMessageHTML(self):
1137 1138
    # test sending a mail message edited as HTML (the default with FCKEditor),
    # then the mail should have HTML.
1139
    text_content = 'Hello<br />World'
1140 1141 1142
    event = self.portal.event_module.newContent(portal_type='Mail Message')
    event.setSource('person_module/me')
    event.setDestination('person_module/recipient')
1143
    event.setContentType('text/html')
1144
    event.setTextContent(text_content)
1145
    self.portal.portal_workflow.doActionFor(event, 'start_action')
1146
    self.tic()
1147
    # content type is kept
1148
    self.assertEqual(event.getContentType(), 'text/html')
1149

1150 1151 1152
    last_message = self.portal.MailHost._last_message
    self.assertNotEquals((), last_message)
    mfrom, mto, messageText = last_message
1153 1154
    self.assertEqual('"Me," <me@erp5.org>', mfrom)
    self.assertEqual(['"Recipient," <recipient@example.com>'], mto)
1155

1156
    message = message_from_string(messageText)
1157 1158
    part = None
    for i in message.get_payload():
1159
      if i.get_content_type()=='text/html':
1160
        part = i
1161
    self.assertNotEqual(part, None)
1162 1163
    self.assertEqual('<html><body>%s</body></html>' % text_content, part.get_payload(decode=True))

1164 1165 1166
  def test_MailMessageEncoding(self):
    # test sending a mail message with non ascii characters
    event = self.portal.event_module.newContent(portal_type='Mail Message')
1167 1168
    event.setSource('person_module/non_ascii_me')
    event.setDestinationList(['person_module/non_ascii_recipient'])
1169 1170
    event.setTitle('Héhé')
    event.setTextContent('Hàhà')
1171
    self.portal.portal_workflow.doActionFor(event, 'start_action')
1172 1173 1174 1175
    self.tic()
    last_message = self.portal.MailHost._last_message
    self.assertNotEquals((), last_message)
    mfrom, mto, messageText = last_message
1176 1177
    self.assertEqual('=?utf-8?q?Me=2C_=F0=9F=90=88_fan?= <me@erp5.org>', mfrom)
    self.assertEqual(['=?utf-8?q?Recipient=2C_=F0=9F=90=88_fan?= <recipient@example.com>'], mto)
1178

1179
    message = message_from_string(messageText)
1180

1181
    self.assertEqual('Héhé', decode_header(message['Subject'])[0][0])
1182 1183
    self.assertEqual('Me, 🐈 fan', decode_header(message['From'])[0][0])
    self.assertEqual('Recipient, 🐈 fan', decode_header(message['To'])[0][0])
1184 1185 1186 1187 1188 1189
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
    self.assertEqual('Hàhà', part.get_payload(decode=True))

Yusei Tahara's avatar
Yusei Tahara committed
1190
  def test_MailAttachmentPdf(self):
Yusei Tahara's avatar
Yusei Tahara committed
1191 1192 1193
    """
    Make sure that pdf document is correctly attached in email
    """
Yusei Tahara's avatar
Yusei Tahara committed
1194
    # Add a document which will be attached.
Yusei Tahara's avatar
Yusei Tahara committed
1195
    # pdf
Nicolas Delaby's avatar
Nicolas Delaby committed
1196 1197 1198
    filename = 'sample_attachment.pdf'
    file_object = makeFileUpload(filename)
    document = self.portal.portal_contributions.newContent(file=file_object)
Yusei Tahara's avatar
Yusei Tahara committed
1199 1200 1201 1202

    self.tic()

    # Add a ticket
1203
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
Yusei Tahara's avatar
Yusei Tahara committed
1204 1205 1206 1207
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1208 1209
                           text_content='Buy this now!',
                           event_workflow_action='plan')
Yusei Tahara's avatar
Yusei Tahara committed
1210 1211

    # Set sender and attach a document to the event.
1212
    event, = self.portal.event_module.objectValues()
Yusei Tahara's avatar
Yusei Tahara committed
1213
    event.edit(source='person_module/me',
1214
               destination='person_module/recipient',
Nicolas Delaby's avatar
Nicolas Delaby committed
1215
               aggregate=document.getRelativeUrl(),
Yusei Tahara's avatar
Yusei Tahara committed
1216 1217 1218 1219 1220
               text_content='This is an advertisement mail.')

    mail_text = event.send(download=True)

    # Check mail text.
1221
    message = message_from_string(mail_text)
Yusei Tahara's avatar
Yusei Tahara committed
1222 1223 1224 1225 1226 1227 1228 1229
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
    self.assertEqual(part.get_payload(decode=True), event.getTextContent())

    # Check attachment
    # pdf
1230
    self.assert_(filename in
Yusei Tahara's avatar
Yusei Tahara committed
1231 1232 1233
                 [i.get_filename() for i in message.get_payload()])
    part = None
    for i in message.get_payload():
Nicolas Delaby's avatar
Nicolas Delaby committed
1234
      if i.get_filename()==filename:
Yusei Tahara's avatar
Yusei Tahara committed
1235
        part = i
Nicolas Delaby's avatar
Nicolas Delaby committed
1236
    self.assertEqual(part.get_payload(decode=True), str(document.getData()))
Yusei Tahara's avatar
Yusei Tahara committed
1237 1238

  def test_MailAttachmentText(self):
Yusei Tahara's avatar
Yusei Tahara committed
1239 1240 1241
    """
    Make sure that text document is correctly attached in email
    """
Yusei Tahara's avatar
Yusei Tahara committed
1242
    # Add a document which will be attached.
Nicolas Delaby's avatar
Nicolas Delaby committed
1243 1244 1245
    filename = 'sample_attachment.odt'
    file_object = makeFileUpload(filename)
    document = self.portal.portal_contributions.newContent(file=file_object)
Yusei Tahara's avatar
Yusei Tahara committed
1246 1247 1248 1249

    self.tic()

    # Add a ticket
Nicolas Delaby's avatar
Nicolas Delaby committed
1250
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
Yusei Tahara's avatar
Yusei Tahara committed
1251 1252 1253 1254
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1255 1256
                           text_content='Buy this now!',
                           event_workflow_action='plan')
Yusei Tahara's avatar
Yusei Tahara committed
1257 1258

    # Set sender and attach a document to the event.
1259
    event, = self.portal.event_module.objectValues()
Yusei Tahara's avatar
Yusei Tahara committed
1260
    event.edit(source='person_module/me',
1261
               destination='person_module/recipient',
Nicolas Delaby's avatar
Nicolas Delaby committed
1262
               aggregate=document.getRelativeUrl(),
Yusei Tahara's avatar
Yusei Tahara committed
1263 1264 1265 1266 1267
               text_content='This is an advertisement mail.')

    mail_text = event.send(download=True)

    # Check mail text.
1268
    message = message_from_string(mail_text)
Yusei Tahara's avatar
Yusei Tahara committed
1269 1270 1271 1272 1273 1274 1275
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
    self.assertEqual(part.get_payload(decode=True), event.getTextContent())

    # Check attachment
Yusei Tahara's avatar
Yusei Tahara committed
1276
    # odt
1277
    self.assert_(filename in
Yusei Tahara's avatar
Yusei Tahara committed
1278 1279 1280
                 [i.get_filename() for i in message.get_payload()])
    part = None
    for i in message.get_payload():
Nicolas Delaby's avatar
Nicolas Delaby committed
1281
      if i.get_filename() == filename:
Yusei Tahara's avatar
Yusei Tahara committed
1282 1283 1284
        part = i
    self.assert_(len(part.get_payload(decode=True))>0)

Yusei Tahara's avatar
Yusei Tahara committed
1285
  def test_MailAttachmentFile(self):
Yusei Tahara's avatar
Yusei Tahara committed
1286 1287 1288
    """
    Make sure that file document is correctly attached in email
    """
Yusei Tahara's avatar
Yusei Tahara committed
1289
    # Add a document which will be attached.
Nicolas Delaby's avatar
Nicolas Delaby committed
1290 1291 1292
    filename = 'sample_attachment.zip'
    file_object = makeFileUpload(filename)
    document = self.portal.portal_contributions.newContent(file=file_object)
Yusei Tahara's avatar
Yusei Tahara committed
1293 1294 1295
    self.tic()

    # Add a ticket
Nicolas Delaby's avatar
Nicolas Delaby committed
1296
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
Yusei Tahara's avatar
Yusei Tahara committed
1297 1298 1299 1300
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1301 1302
                           text_content='Buy this now!',
                           event_workflow_action='plan')
Yusei Tahara's avatar
Yusei Tahara committed
1303 1304

    # Set sender and attach a document to the event.
1305
    event, = self.portal.event_module.objectValues()
Yusei Tahara's avatar
Yusei Tahara committed
1306
    event.edit(source='person_module/me',
1307
               destination='person_module/recipient',
Nicolas Delaby's avatar
Nicolas Delaby committed
1308
               aggregate=document.getRelativeUrl(),
Yusei Tahara's avatar
Yusei Tahara committed
1309 1310 1311 1312
               text_content='This is an advertisement mail.')

    mail_text = event.send(download=True)
    # Check mail text.
1313
    message = message_from_string(mail_text)
Yusei Tahara's avatar
Yusei Tahara committed
1314 1315 1316 1317 1318 1319 1320 1321
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
    self.assertEqual(part.get_payload(decode=True), event.getTextContent())

    # Check attachment
    # zip
1322
    self.assert_(filename in
Yusei Tahara's avatar
Yusei Tahara committed
1323 1324 1325
                 [i.get_filename() for i in message.get_payload()])
    part = None
    for i in message.get_payload():
Nicolas Delaby's avatar
Nicolas Delaby committed
1326
      if i.get_filename() == filename:
Yusei Tahara's avatar
Yusei Tahara committed
1327 1328 1329 1330
        part = i
    self.assert_(len(part.get_payload(decode=True))>0)

  def test_MailAttachmentImage(self):
Yusei Tahara's avatar
Yusei Tahara committed
1331 1332 1333
    """
    Make sure that image document is correctly attached in email
    """
Yusei Tahara's avatar
Yusei Tahara committed
1334
    # Add a document which will be attached.
Nicolas Delaby's avatar
Nicolas Delaby committed
1335 1336 1337
    filename = 'sample_attachment.gif'
    file_object = makeFileUpload(filename)
    document = self.portal.portal_contributions.newContent(file=file_object)
Yusei Tahara's avatar
Yusei Tahara committed
1338 1339 1340 1341

    self.tic()

    # Add a ticket
Nicolas Delaby's avatar
Nicolas Delaby committed
1342
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
Yusei Tahara's avatar
Yusei Tahara committed
1343 1344 1345 1346
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1347 1348
                           text_content='Buy this now!',
                           event_workflow_action='plan')
Yusei Tahara's avatar
Yusei Tahara committed
1349 1350

    # Set sender and attach a document to the event.
1351
    event, = self.portal.event_module.objectValues()
Yusei Tahara's avatar
Yusei Tahara committed
1352
    event.edit(source='person_module/me',
1353
               destination='person_module/recipient',
Nicolas Delaby's avatar
Nicolas Delaby committed
1354
               aggregate=document.getRelativeUrl(),
Yusei Tahara's avatar
Yusei Tahara committed
1355 1356 1357 1358 1359
               text_content='This is an advertisement mail.')

    mail_text = event.send(download=True)

    # Check mail text.
1360
    message = message_from_string(mail_text)
Yusei Tahara's avatar
Yusei Tahara committed
1361 1362 1363 1364 1365 1366 1367 1368
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
    self.assertEqual(part.get_payload(decode=True), event.getTextContent())

    # Check attachment
    # gif
1369
    self.assert_(filename in
Yusei Tahara's avatar
Yusei Tahara committed
1370 1371 1372
                 [i.get_filename() for i in message.get_payload()])
    part = None
    for i in message.get_payload():
Nicolas Delaby's avatar
Nicolas Delaby committed
1373
      if i.get_filename() == filename:
Yusei Tahara's avatar
Yusei Tahara committed
1374
        part = i
Nicolas Delaby's avatar
Nicolas Delaby committed
1375
    self.assertEqual(part.get_payload(decode=True), str(document.getData()))
Yusei Tahara's avatar
Yusei Tahara committed
1376 1377

  def test_MailAttachmentWebPage(self):
Yusei Tahara's avatar
Yusei Tahara committed
1378 1379 1380
    """
    Make sure that webpage document is correctly attached in email
    """
Yusei Tahara's avatar
Yusei Tahara committed
1381
    # Add a document which will be attached.
Nicolas Delaby's avatar
Nicolas Delaby committed
1382 1383 1384 1385
    filename = 'sample_attachment.html'
    document = self.portal.portal_contributions.newContent(
                          data='<html><body>Hello world!</body></html>',
                          filename=filename)
Yusei Tahara's avatar
Yusei Tahara committed
1386 1387 1388
    self.tic()

    # Add a ticket
Nicolas Delaby's avatar
Nicolas Delaby committed
1389
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
Yusei Tahara's avatar
Yusei Tahara committed
1390 1391 1392 1393
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1394 1395
                           text_content='Buy this now!',
                           event_workflow_action='plan')
Yusei Tahara's avatar
Yusei Tahara committed
1396 1397

    # Set sender and attach a document to the event.
1398
    event, = self.portal.event_module.objectValues()
Yusei Tahara's avatar
Yusei Tahara committed
1399
    event.edit(source='person_module/me',
1400
               destination='person_module/recipient',
Nicolas Delaby's avatar
Nicolas Delaby committed
1401
               aggregate=document.getRelativeUrl(),
Yusei Tahara's avatar
Yusei Tahara committed
1402 1403 1404 1405 1406
               text_content='This is an advertisement mail.')

    mail_text = event.send(download=True)

    # Check mail text.
1407
    message = message_from_string(mail_text)
Yusei Tahara's avatar
Yusei Tahara committed
1408 1409 1410 1411 1412 1413 1414 1415
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
    self.assertEqual(part.get_payload(decode=True), event.getTextContent())

    # Check attachment
    # html
1416
    self.assert_(filename in
Yusei Tahara's avatar
Yusei Tahara committed
1417 1418 1419
                 [i.get_filename() for i in message.get_payload()])
    part = None
    for i in message.get_payload():
Nicolas Delaby's avatar
Nicolas Delaby committed
1420
      if i.get_filename() == filename:
Yusei Tahara's avatar
Yusei Tahara committed
1421
        part = i
1422
    self.assertEqual(part.get_payload(decode=True),
Nicolas Delaby's avatar
Nicolas Delaby committed
1423
                     str(document.getTextContent()))
1424
    self.assertEqual(part.get_content_type(), 'text/html')
Yusei Tahara's avatar
Yusei Tahara committed
1425

Aurel's avatar
Aurel committed
1426 1427 1428 1429 1430
  def test_MailRespond(self):
    """
    Test we can answer an incoming event and quote it
    """
    # Add a ticket
Nicolas Delaby's avatar
Nicolas Delaby committed
1431
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
Aurel's avatar
Aurel committed
1432 1433 1434 1435
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1436 1437
                           text_content='Buy this now!',
                           event_workflow_action='deliver')
Yusei Tahara's avatar
Yusei Tahara committed
1438

Aurel's avatar
Aurel committed
1439
    # Set sender and attach a document to the event.
1440
    event, = self.portal.event_module.objectValues()
Aurel's avatar
Aurel committed
1441
    event.edit(source='person_module/me',
1442
               destination='person_module/recipient',
Aurel's avatar
Aurel committed
1443 1444
               text_content='This is an advertisement mail.')
    first_event_id = event.getId()
1445 1446 1447 1448 1449
    event.Event_createResponse(response_event_portal_type='Mail Message',
                               response_event_title='Answer',
                               response_event_text_content='> This is an advertisement mail.',
                               response_workflow_action='send',
                               )
Aurel's avatar
Aurel committed
1450

1451
    self.assertEqual(event.getSimulationState(), "delivered")
1452

Aurel's avatar
Aurel committed
1453 1454 1455 1456 1457 1458 1459
    # answer event must have been created
    self.assertEqual(len(self.portal.event_module), 2)
    for ev in self.portal.event_module.objectValues():
      if ev.getId() != first_event_id:
        answer_event = ev

    # check properties of answer event
1460
    self.assertEqual(answer_event.getSimulationState(), "started")
Aurel's avatar
Aurel committed
1461 1462
    self.assertEqual(answer_event.getCausality(), event.getRelativeUrl())
    self.assertEqual(answer_event.getDestination(), 'person_module/me')
1463
    self.assertEqual(answer_event.getSource(), 'person_module/recipient')
Aurel's avatar
Aurel committed
1464
    self.assertEqual(answer_event.getTextContent(), '> This is an advertisement mail.')
1465 1466
    self.assertEqual(answer_event.getFollowUpValue(), ticket)
    self.assert_(answer_event.getData() is not None)
Yusei Tahara's avatar
Yusei Tahara committed
1467

1468 1469 1470 1471 1472 1473
  def test_MailAttachmentFileWithoutDMS(self):
    """
    Make sure that file document is correctly attached in email
    """
    # Add a document on a person which will be attached.

Nicolas Delaby's avatar
Nicolas Delaby committed
1474
    def add_document(filename, container, portal_type):
1475
      f = makeFileUpload(filename)
Nicolas Delaby's avatar
Nicolas Delaby committed
1476
      document = container.newContent(portal_type=portal_type)
1477 1478
      document.edit(file=f, reference=filename)
      return document
Nicolas Delaby's avatar
Nicolas Delaby committed
1479
    filename = 'sample_attachment.txt'
1480
    # txt
Nicolas Delaby's avatar
Nicolas Delaby committed
1481
    document_txt = add_document(filename,
1482
                                self.portal.person_module['me'], 'Embedded File')
1483 1484 1485 1486

    self.tic()

    # Add a ticket
Nicolas Delaby's avatar
Nicolas Delaby committed
1487
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
1488 1489 1490 1491
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1492 1493
                           text_content='Buy this now!',
                           event_workflow_action='plan')
1494 1495

    # Set sender and attach a document to the event.
1496
    event, = self.portal.event_module.objectValues()
1497
    event.edit(source='person_module/me',
1498
               destination='person_module/recipient',
1499 1500 1501 1502 1503 1504
               aggregate=document_txt.getRelativeUrl(),
               text_content='This is an advertisement mail.')

    mail_text = event.send(download=True)

    # Check mail text.
1505
    message = message_from_string(mail_text)
1506 1507 1508 1509
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
1510
        break
1511 1512 1513 1514
    self.assertEqual(part.get_payload(decode=True), event.getTextContent())

    # Check attachment
    # txt
1515
    self.assert_(filename in
1516 1517 1518
                 [i.get_filename() for i in message.get_payload()])
    part = None
    for i in message.get_payload():
Nicolas Delaby's avatar
Nicolas Delaby committed
1519
      if i.get_filename() == filename:
1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530
        part = i
    self.assert_(len(part.get_payload(decode=True))>0)



  def test_MailAttachmentImageWithoutDMS(self):
    """
    Make sure that image document is correctly attached in email without dms
    """
    # Add a document on a person which will be attached.

Nicolas Delaby's avatar
Nicolas Delaby committed
1531
    def add_document(filename, container, portal_type):
1532
      f = makeFileUpload(filename)
Nicolas Delaby's avatar
Nicolas Delaby committed
1533
      document = container.newContent(portal_type=portal_type)
1534 1535 1536 1537
      document.edit(file=f, reference=filename)
      return document

    # gif
Nicolas Delaby's avatar
Nicolas Delaby committed
1538 1539
    filename = 'sample_attachment.gif'
    document_gif = add_document(filename,
1540
                                self.portal.person_module['me'], 'Embedded File')
1541 1542 1543 1544

    self.tic()

    # Add a ticket
Nicolas Delaby's avatar
Nicolas Delaby committed
1545
    ticket = self.portal.campaign_module.newContent(portal_type='Campaign',
1546 1547 1548 1549
                                                    title='Advertisement')
    # Create a event
    ticket.Ticket_newEvent(portal_type='Mail Message',
                           title='Our new product',
1550 1551
                           text_content='Buy this now!',
                           event_workflow_action='plan')
1552 1553

    # Set sender and attach a document to the event.
1554
    event, = self.portal.event_module.objectValues()
1555
    event.edit(source='person_module/me',
1556
               destination='person_module/recipient',
1557 1558 1559 1560 1561 1562
               aggregate=document_gif.getRelativeUrl(),
               text_content='This is an advertisement mail.')

    mail_text = event.send(download=True)

    # Check mail text.
1563
    message = message_from_string(mail_text)
1564 1565 1566 1567 1568 1569 1570 1571
    part = None
    for i in message.get_payload():
      if i.get_content_type()=='text/plain':
        part = i
    self.assertEqual(part.get_payload(decode=True), event.getTextContent())

    # Check attachment
    # gif
1572
    self.assert_(filename in
1573 1574 1575
                 [i.get_filename() for i in message.get_payload()])
    part = None
    for i in message.get_payload():
Nicolas Delaby's avatar
Nicolas Delaby committed
1576
      if i.get_filename() == filename:
1577 1578 1579
        part = i
    self.assertEqual(part.get_payload(decode=True), str(document_gif.getData()))

1580 1581 1582 1583 1584
  def test_cloneEvent(self):
    """
      All events uses after script and interaciton
      workflow add a test for clone
    """
1585 1586
    # XXX in the case of title, getTitle ignores the title attribute,
    # if any data is stored. In the case of text_content, getTextContent
1587
    # respects the behaviour is the same as Title.
1588 1589 1590 1591 1592
    portal_type = 'Mail Message'
    dummy_title = 'Dummy title'
    real_title = 'Real Title'
    dummy_content = 'Dummy content'
    real_content = 'Real content'
1593
    event = self.portal.event_module.newContent(portal_type=portal_type,
1594
                                                title=dummy_title,
1595
                                                text_content=dummy_content,)
1596
    self.assertFalse(event.hasFile(), '%r has a file' % (event,))
1597 1598
    self.assertEqual(event.getTitle(), dummy_title)
    self.assertEqual(event.getTextContent(), dummy_content)
1599

1600
    event.setData('Subject: %s\r\n\r\n%s' % (real_title, real_content))
1601
    self.assertTrue(event.hasFile(), '%r has no file' % (event,))
1602 1603
    self.assertEqual(event.getTitle(), real_title)
    self.assertEqual(event.getTextContent(), real_content)
1604

1605
    self.tic()
1606
    new_event = event.Base_createCloneDocument(batch_mode=1)
1607
    self.assertFalse(new_event.hasFile(), '%r has a file' % (new_event,))
1608 1609 1610
    self.assertEqual(new_event.getData(), '')
    self.assertEqual(new_event.getTitle(), real_title)
    self.assertEqual(new_event.getTextContent(), real_content)
1611
    self.assertNotEquals(new_event.getReference(), event.getReference())
1612

1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626
  def test_cloneTicketAndEventList(self):
    """
      All events uses after script and interaciton
      workflow add a test for clone
    """
    portal = self.portal
    event_list = []
    destination_list = []
    for i in range (0,100):
      person = portal.person_module.newContent(
                 portal_type='Person',
                 title = 'Person %s' %i)
      destination_list.append(person)
    campaing = portal.campaign_module.newContent(
1627
                 portal_type='Campaign',
1628 1629 1630 1631
                 reference = 'Test')
    for i in range(0,3):
      event = portal.event_module.newContent(
                portal_type='Mail Message',
1632
                title = 'Mail %s' %i,
1633 1634 1635
                follow_up = campaing.getRelativeUrl())
      event.setDestinationList([x.getRelativeUrl() for x in destination_list])
      event_list.append(event)
1636
    self.tic()
1637

1638 1639
    # use Ticket_cloneTicketAndEventList
    campaing.Ticket_cloneTicketAndEventList()
1640
    self.tic()
1641 1642 1643 1644 1645 1646 1647 1648 1649
    cloned_campaign = [x for x in portal.campaign_module.objectValues() if x!=campaing][0]
    cloned_event_list = [x for x in portal.event_module.objectValues() if x.getFollowUpValue()==cloned_campaign]
    self.assertEqual(campaing.getTitle(), cloned_campaign.getTitle())
    self.assertEqual(campaing.getReference(), cloned_campaign.getReference())

    for i in range(0,3):
      self.assertSameSet(event_list[i].getDestinationValueList(), cloned_event_list[i].getDestinationValueList())


Nicolas Delaby's avatar
Nicolas Delaby committed
1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697
  def test_Base_addEvent(self):
    """Check Base_addEvent script with a logged in user.
    """
    # create categories.
    resource = self.portal.portal_categories.resource
    for i in range(3):
      resource.newContent(portal_type='Category',
                          title='Title%s' % i,
                          id=i)
    self.portal.portal_categories.function.newContent(portal_type='Category',
                                                      id='crm_agent')
    # create user and configure security settings
    portal_type_list = self.portal.getPortalEventTypeList()\
                       + self.portal.getPortalEntityTypeList()\
                       + ('Event Module',)
    for portal_type in portal_type_list:
      portal_type_object = getattr(self.portal.portal_types, portal_type)
      portal_type_object.newContent(id='manager_role',
                                    portal_type='Role Information',
                                    role_name_list=('Manager',),
                                    role_category_list='function/crm_agent')
      portal_type_object.updateRoleMapping()
    user = self.createSimpleUser('Agent', 'crm_agent', 'crm_agent')
    self.tic()
    try:
      # create entites
      organisation_portal_type = 'Organisation'
      person_portal_type = 'Person'
      my_company = self.portal.getDefaultModule(organisation_portal_type)\
                              .newContent(portal_type=organisation_portal_type,
                                          title='Software provider')
      organisation = self.portal.getDefaultModule(organisation_portal_type)\
                              .newContent(portal_type=organisation_portal_type,
                                          title='Soap Service Express')
      person = self.portal.getDefaultModule(person_portal_type).newContent(
                              portal_type=person_portal_type,
                              first_name='John',
                              last_name='Doe',
                              default_email_text='john.doe@example.com',
                              default_career_subordination_value=organisation)
      another_person = self.portal.getDefaultModule(person_portal_type)\
                  .newContent(portal_type=person_portal_type,
                              first_name='Jane',
                              last_name='Doe',
                              default_email_text='jane.doe@example.com',
                              default_career_subordination_value=organisation)
      user.setDefaultCareerSubordinationValue(my_company)
      # log in user
1698
      self.loginByUserName('crm_agent')
Nicolas Delaby's avatar
Nicolas Delaby committed
1699 1700 1701 1702

      ### Incoming on Person ###
      # Submit the dialog on person
      title = 'Incoming email'
1703
      direction = 'incoming'
Nicolas Delaby's avatar
Nicolas Delaby committed
1704
      portal_type = 'Note'
Nicolas Delaby's avatar
Nicolas Delaby committed
1705
      resource = resource['1'].getCategoryRelativeUrl()
Nicolas Delaby's avatar
Nicolas Delaby committed
1706 1707 1708 1709 1710 1711 1712
      person.Base_addEvent(title, direction, portal_type, resource)

      # Index Event
      self.tic()

      # check created Event
      event = person.getSourceRelatedValue()
1713 1714
      self.assertEqual(event.getTitle(), title)
      self.assertEqual(event.getResource(), resource)
Nicolas Delaby's avatar
Nicolas Delaby committed
1715
      self.assertTrue(event.hasStartDate())
1716 1717
      self.assertEqual(event.getSource(), person.getRelativeUrl())
      self.assertEqual(event.getSourceSection(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1718
                        organisation.getRelativeUrl())
1719 1720
      self.assertEqual(event.getDestination(), user.getRelativeUrl())
      self.assertEqual(event.getDestinationSection(), user.getSubordination())
Nicolas Delaby's avatar
Nicolas Delaby committed
1721 1722 1723 1724

      ### Outgoing on Person ###
      # Check another direction
      title = 'Outgoing email'
1725
      direction = 'outgoing'
Nicolas Delaby's avatar
Nicolas Delaby committed
1726 1727 1728 1729 1730 1731 1732
      another_person.Base_addEvent(title, direction, portal_type, resource)

      # Index Event
      self.tic()

      # check created Event
      event = another_person.getDestinationRelatedValue()
1733 1734
      self.assertEqual(event.getTitle(), title)
      self.assertEqual(event.getResource(), resource)
Nicolas Delaby's avatar
Nicolas Delaby committed
1735
      self.assertTrue(event.hasStartDate())
1736
      self.assertEqual(event.getDestination(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1737
                        another_person.getRelativeUrl())
1738
      self.assertEqual(event.getDestinationSection(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1739
                        organisation.getRelativeUrl())
1740 1741
      self.assertEqual(event.getSource(), user.getRelativeUrl())
      self.assertEqual(event.getSourceSection(), user.getSubordination())
Nicolas Delaby's avatar
Nicolas Delaby committed
1742 1743 1744 1745 1746 1747 1748 1749 1750 1751

      ### Outgoing on Organisation ###
      # check on Organisation
      event = organisation.Base_addEvent(title, direction,
                                         portal_type, resource)

      # Index Event
      self.tic()

      # check created Event
1752 1753
      _, event = sorted(organisation.getDestinationSectionRelatedValueList(),
          key=lambda x: x.getCreationDate())
1754 1755
      self.assertEqual(event.getTitle(), title)
      self.assertEqual(event.getResource(), resource)
Nicolas Delaby's avatar
Nicolas Delaby committed
1756
      self.assertTrue(event.hasStartDate())
1757
      self.assertEqual(event.getDestination(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1758
                        organisation.getRelativeUrl())
1759
      self.assertEqual(event.getDestinationSection(),
Nicolas Delaby's avatar
Nicolas Delaby committed
1760
                        organisation.getRelativeUrl())
1761 1762
      self.assertEqual(event.getSource(), user.getRelativeUrl())
      self.assertEqual(event.getSourceSection(), user.getSubordination())
Nicolas Delaby's avatar
Nicolas Delaby committed
1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773

      ### Outgoing on Career ###
      # Now check Base_addEvent on any document (follow_up)
      career = person.default_career
      career.Base_addEvent(title, direction, portal_type, resource)

      # Index Event
      self.tic()

      # check created Event
      event = career.getFollowUpRelatedValue()
1774 1775
      self.assertEqual(event.getTitle(), title)
      self.assertEqual(event.getResource(), resource)
Nicolas Delaby's avatar
Nicolas Delaby committed
1776
      self.assertTrue(event.hasStartDate())
1777 1778
      self.assertEqual(event.getSource(), user.getRelativeUrl())
      self.assertEqual(event.getSourceSection(), user.getSubordination())
Nicolas Delaby's avatar
Nicolas Delaby committed
1779 1780
    finally:
      # clean up created roles on portal_types
1781
      self.login() # admin
Nicolas Delaby's avatar
Nicolas Delaby committed
1782 1783 1784 1785 1786
      for portal_type in portal_type_list:
        portal_type_object = getattr(self.portal.portal_types, portal_type)
        portal_type_object._delObject('manager_role')
        portal_type_object.updateRoleMapping()
      self.tic()
1787

1788 1789 1790 1791 1792 1793 1794
  def test_MailMessage_Event_send_generate_activity_list(self):
    """
      Check that after post a Mail Message, the activities are generated
      correctly
    """
    person = self.portal.person_module.newContent(portal_type="Person")
    person.edit(default_email_text="test@test.com", title="test%s" % person.getId())
1795
    self.tic()
1796 1797
    mail_message = self.portal.event_module.newContent(portal_type="Mail Message")
    relative_url_list = [z.getRelativeUrl() for z in self.portal.person_module.searchFolder()]
1798
    self.assertEqual(5, len(relative_url_list))
1799 1800 1801 1802
    mail_message.setDestinationList(relative_url_list)
    mail_message.setSource(relative_url_list[0])
    mail_text_content = "Body Text Content"
    mail_message.setTextContent(mail_text_content)
1803 1804 1805
    # directly call MailMessage_send to pass a packet size of 1, so that we
    # have one activity per recipient
    mail_message.MailMessage_send(packet_size=1)
1806
    self.commit()
1807
    portal_activities = self.portal.portal_activities
1808 1809 1810 1811
    portal_activities.manageInvoke(object_path=mail_message.getPath(),
      method_id='immediateReindexObject')
    portal_activities.manageInvoke(object_path=mail_message.getPath(),
      method_id='MailMessage_sendByActivity')
1812
    self.commit()
1813 1814 1815
    message_list = [i for i in portal_activities.getMessageList() \
                    if i.kw.has_key("event_relative_url")]
    try:
1816 1817
      # 5 recipients -> 5 activities
      self.assertEqual(5, len(message_list))
1818
    finally:
1819
      self.tic()
1820

1821
    self.assertEqual(5, len(self.portal.MailHost._message_list))
1822 1823 1824 1825
    for message_info in self.portal.MailHost._message_list:
      self.assertTrue(mail_text_content in message_info[-1])
      message = message_from_string(message_info[-1])
      self.assertTrue(DateTime(message.get("Date")).isCurrentDay())
1826

1827
  def test_MailMessage_send_simple_case(self):
1828
    """
1829
      Check that the method send send one email passing all parameters directly
1830 1831 1832
      from_url, to_url, reply_url, subject, body, attachment_format, attachment_list
    """
    mail_message = self.portal.event_module.newContent(portal_type="Mail Message")
1833
    self.tic()
1834 1835 1836 1837 1838
    mail_message.send(from_url='FG ER <eee@eee.com>',
                      to_url='Expert User <expert@in24.test>',
                      subject="Simple Case",
                      body="Body Simple Case",
                      attachment_list=[])
1839
    self.tic()
1840
    (from_url, to_url, last_message,), = self.portal.MailHost._message_list
1841
    self.assertTrue("Body Simple Case" in last_message)
1842 1843
    self.assertEqual('FG ER <eee@eee.com>', from_url)
    self.assertEqual(['Expert User <expert@in24.test>'], to_url)
1844

1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858
  def test_MailMessage_send_extra_headers(self):
    """Test sending message with extra headers
    """
    mail_message = self.portal.event_module.newContent(
        portal_type="Mail Message",
        source='person_module/me',
        destination='person_module/recipient')

    mail_message.send(extra_header_dict={"X-test-header": "test"})
    self.tic()
    (from_url, to_url, last_message,), = self.portal.MailHost._message_list
    message = message_from_string(last_message)
    self.assertEqual("test", message.get("X-test-header"))

1859

1860 1861
def test_suite():
  suite = unittest.TestSuite()
1862
  suite.addTest(unittest.makeSuite(TestCRM))
1863
  suite.addTest(unittest.makeSuite(TestCRMMailIngestion))
Yusei Tahara's avatar
Yusei Tahara committed
1864
  suite.addTest(unittest.makeSuite(TestCRMMailSend))
1865
  return suite