Example #1
0
    def test_no_previous_email_yet_one_old_and_one_new_charge_yields_only_the_newer_charge_in_the_email(
            self):

        store_writer = Mock()
        store_writer.get_from_binary.side_effect = stub(
            (call('charges'), {
                "charges": PIHUT_CHARGE,
                "most_recent_seen": 1460184000
            }), (call('most-recently-seen'), 1460184000))
        store_writer.store_as_binary.side_effect = stub((call(
            'charges', {
                'charges': PIMORONI_CHARGE_WITH_WHEN_STR,
                'most_recent_seen': 1460184000
            }), True), (call('most-recently-seen', 1460184000), True))

        digester = ChargeCardDigester(store_writer)  ## What we are testing

        expected_payload = """<table>
  <tr style="background-color: #acf;">
    <th>Type</th><th>Vendor</th><th>When</th><th>Curr</th><th>Amt</th><th>Card</th>
  </tr>
  <tr style="">
    <td>Charge</td>
    <td>Pimoroni</td>
    <td>Apr&nbsp;09 02:56</td>
    <td>GBP</td>
    <td style="text-align: right;"><b>4</b></td>
    <td>Amex 1234</td>
  </tr>
</table>"""

        expected_message = ("Subject: Spending Digest\n" + MAIL_HDR +
                            expected_payload +
                            "\n\n-----NOTIFICATION_BOUNDARY-5678")

        digest_inbox_proxy = Mock()
        digest_inbox_proxy.delete_previous_message.side_effect = stub(
            (call(), True))
        digest_inbox_proxy.append.side_effect = stub(
            (call(expected_message), True))

        digester.new_charge_summary = PIMORONI_CHARGE

        digester.notification_boundary_rand = "5678"
        digester.rewrite_digest_emails(digest_inbox_proxy, False, False,
                                       "*****@*****.**")

        self.assertEqual(digest_inbox_proxy.mock_calls,
                         [call.append(expected_message)])

        calls = store_writer.mock_calls
        self.assertEqual(calls, [
            call.get_from_binary('charges'),
            call.store_as_binary(
                'charges', {
                    'charges': PIMORONI_CHARGE_WITH_WHEN_STR,
                    'most_recent_seen': 1460184000
                })
        ])
    def test_no_previous_email_yet_one_old_and_one_new_charge_yields_only_the_newer_charge_in_the_email(self):

        store_writer = Mock()
        store_writer.get_from_binary.side_effect = stub(
            (call('charges'), {
                "charges": PIHUT_CHARGE,
                "most_recent_seen": 1460184000
            }),
            (call('most-recently-seen'), 1460184000)
        )
        store_writer.store_as_binary.side_effect = stub(
            (call('charges', {
                'charges': PIMORONI_CHARGE_WITH_WHEN_STR,
                'most_recent_seen': 1460184000
            }), True),
            (call('most-recently-seen', 1460184000), True)
        )

        digester = ChargeCardDigester(store_writer)  ## What we are testing

        expected_payload = """<table>
  <tr style="background-color: #acf;">
    <th>Type</th><th>Vendor</th><th>When</th><th>Curr</th><th>Amt</th><th>Card</th>
  </tr>
  <tr style="">
    <td>Charge</td>
    <td>Pimoroni</td>
    <td>Apr&nbsp;09 02:56</td>
    <td>GBP</td>
    <td style="text-align: right;"><b>4</b></td>
    <td>Amex 1234</td>
  </tr>
</table>"""

        expected_message = ("Subject: Spending Digest\n"
                            + MAIL_HDR + expected_payload + "\n\n-----NOTIFICATION_BOUNDARY-5678")

        digest_inbox_proxy = Mock()
        digest_inbox_proxy.delete_previous_message.side_effect = stub((call(), True))
        digest_inbox_proxy.append.side_effect = stub((call(expected_message), True))

        digester.new_charge_summary = PIMORONI_CHARGE

        digester.notification_boundary_rand = "5678"
        digester.rewrite_digest_emails(digest_inbox_proxy, False, False, "*****@*****.**")

        self.assertEquals(digest_inbox_proxy.mock_calls,
                          [call.append(expected_message)])

        calls = store_writer.mock_calls
        self.assertEquals(calls, [
            call.get_from_binary('charges'),
            call.store_as_binary('charges', {
                'charges': PIMORONI_CHARGE_WITH_WHEN_STR,
                'most_recent_seen': 1460184000
            })
        ])
    def doit_with_three_incoming_emails(self, userId, expected_message, stored_notifs_expectations, expected_message_ids):

        notification_store = {}

        final_notifs_store = NotifsStore()

        store_writer = Mock()
        store_writer.get_from_binary.side_effect = stub(
            (call('reddit-notifications'), notification_store),
            (call('reddit-recently-seen'), 0)
        )
        store_writer.store_as_binary.side_effect = stub(
            (call('reddit-notifications', final_notifs_store), True),
            (call('reddit-recently-seen', 0), True)
        )

        with open("sampleEmail.txt", "r") as testEmailFile:
            testEmail1 = testEmailFile.read()

            with open("sampleEmail2.txt", "r") as testEmailFile2:
                testEmail2 = testEmailFile2.read()

                with open("sampleEmail3.txt", "r") as testEmailFile3:
                    testEmail3 = testEmailFile3.read()

                    digest_inbox_proxy = Mock()
                    digest_inbox_proxy.delete_previous_message.side_effect = stub((call(), True))
                    digest_inbox_proxy.append.side_effect = stub((call(expected_message), True))

                    digesters = []
                    digester = RedditNotificationDigester(store_writer, userId=userId)  ## What we are testing
                    digester.notification_boundary_rand = "-5678"  # no random number for the email's notification boundary
                    digesters.append(digester)

                    digestion_processor = DigestionProcessor(None, None, digesters, False, "*****@*****.**", False, "INBOX")

                    unmatched_to_move = []
                    to_delete_from_notification_folder = []

                    digestion_processor.process_incoming_notification(1234, digesters, testEmail1, to_delete_from_notification_folder, unmatched_to_move, False)
                    digestion_processor.process_incoming_notification(1235, digesters, testEmail2, to_delete_from_notification_folder, unmatched_to_move, False)
                    digestion_processor.process_incoming_notification(1236, digesters, testEmail3, to_delete_from_notification_folder, unmatched_to_move, False)

                    digester.rewrite_digest_emails(digest_inbox_proxy, has_previous_message=True,
                                                   previously_seen=False, sender_to_implicate="*****@*****.**")
                    self.assertEqual(digest_inbox_proxy.mock_calls, [call.delete_previous_message(), call.append(expected_message)])

                    self.assertEqual(store_writer.mock_calls, [
                        call.get_from_binary('reddit-notifications'),
                        call.get_from_binary('reddit-recently-seen'),
                        call.store_as_binary('reddit-notifications', stored_notifs_expectations),
                        call.store_as_binary('reddit-recently-seen', 0)])
                    self.assertEqual(len(unmatched_to_move), 0)
                    self.assertEqual(str(to_delete_from_notification_folder), expected_message_ids)
                    self.assertEqual(len(final_notifs_store.notifs), 4)
Example #4
0
def test_lookup():
    with pytest.raises(KeyError):
        stub()._lookup(sentinel.keya)

    test_data = stub((sentinel.keya, sentinel.vala), (sentinel.keyb, sentinel.valb))

    assert sentinel.vala == test_data._lookup(sentinel.keya)

    assert sentinel.valb == test_data._lookup(sentinel.keyb)

    with pytest.raises(KeyError):
        test_data._lookup(sentinel.keyc)
Example #5
0
def test_lookup():
    test_data = stub((sentinel.keya, sentinel.vala),
                     (sentinel.keyb, sentinel.valb))

    assert sentinel.vala == test_data._lookup(sentinel.keya)

    assert sentinel.valb == test_data._lookup(sentinel.keyb)
Example #6
0
def test_stub_sequence_of_results():
    mock_fn = Mock()
    mock_fn.side_effect = stub((call(sentinel.argfoo), sentinel.foo,
                                                       sentinel.bar,
                                                       RuntimeError,
                                                       RuntimeError(sentinel.boom),
                                                       sentinel.all_ok_now))

    assert mock_fn(sentinel.argfoo) == sentinel.foo

    assert mock_fn(sentinel.argfoo) == sentinel.bar

    with pytest.raises(RuntimeError):
        mock_fn(sentinel.argfoo)

    try:
        mock_fn(sentinel.argfoo)
    except RuntimeError as e:
        assert e.message == sentinel.boom
    else:
        assert False

    assert mock_fn(sentinel.argfoo) == sentinel.all_ok_now

    with pytest.raises(StopIteration):
        mock_fn(sentinel.argfoo)
Example #7
0
def test_error_on_missed_lookup():
    test_data = stub((sentinel.keya, sentinel.vala), (sentinel.keyb, sentinel.valb))

    with pytest.raises(UnexpectedStubCall) as err:
        test_data._lookup(sentinel.keyc)

    assert str(err.value) == """Unexpected stub call:
Example #8
0
def test_error_on_missed_lookup():
    test_data = stub((sentinel.keya, sentinel.vala),
                     (sentinel.keyb, sentinel.valb))

    with pytest.raises(UnexpectedStubCall) as err:
        test_data._lookup(sentinel.keyc)

    assert str(err.value) == """Unexpected stub call:
Example #9
0
def test_universal_side_effect():
    st = stub()

    with patch.object(st, "_lookup") as mock_lookup:  #@UndefinedVariable
        with patch("mockextras._stub.call") as mock_callargs:
            assert mock_lookup.return_value == st(sentinel.arg1, sentinel.arg2)

    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
Example #10
0
def test_universal_side_effect():
    st = stub()

    with patch.object(st, "_lookup") as mock_lookup: #@UndefinedVariable
        with patch("mockextras._stub.call") as mock_callargs:
            assert mock_lookup.return_value == st(sentinel.arg1, sentinel.arg2)

    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
Example #11
0
def test_stub_exception():
    st = stub()

    with patch.object(st, "_lookup", return_value=RuntimeError) as mock_lookup: #@UndefinedVariable
        with patch("mockextras._stub.call") as mock_callargs:
            with pytest.raises(RuntimeError):
                st(sentinel.arg1, sentinel.arg2)

    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
Example #12
0
def test_stub_missing_case():
    st = stub()

    with patch.object(st, "_lookup", side_effect=KeyError) as mock_lookup: #@UndefinedVariableF
        with patch("mockextras._stub.call") as mock_callargs:
            with pytest.raises(KeyError):
                assert st(sentinel.arg1, sentinel.arg2)

    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
Example #13
0
def test_stub_missing_case():
    st = stub()

    with patch.object(st, "_lookup", side_effect=UnexpectedStubCall
                      ) as mock_lookup:  #@UndefinedVariableF
        with patch("mockextras._stub.call") as mock_callargs:
            with pytest.raises(UnexpectedStubCall):
                assert st(sentinel.arg1, sentinel.arg2)

    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
Example #14
0
def test_stub_exception():
    st = stub()

    with patch.object(
            st, "_lookup",
            return_value=RuntimeError) as mock_lookup:  #@UndefinedVariable
        with patch("mockextras._stub.call") as mock_callargs:
            with pytest.raises(RuntimeError):
                st(sentinel.arg1, sentinel.arg2)

    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
Example #15
0
def test_stub_sequence():
    st = stub()

    with patch.object(st, "_lookup") as mock_lookup: #@UndefinedVariable
        with patch("mockextras._stub.call") as mock_callargs:
            with patch("mockextras._stub.isinstance", create=True, return_value=True) as mock_isinstance:
                assert mock_lookup.return_value.return_value == st(sentinel.arg1, sentinel.arg2)

    mock_isinstance.assert_called_once_with(mock_lookup.return_value, _Sequence)
    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
    mock_lookup.return_value.assert_called_once_with()
Example #16
0
def test_stub_sequence():
    st = stub()

    with patch.object(st, "_lookup") as mock_lookup:  #@UndefinedVariable
        with patch("mockextras._stub.call") as mock_callargs:
            with patch("mockextras._stub.isinstance",
                       create=True,
                       return_value=True) as mock_isinstance:
                assert mock_lookup.return_value.return_value == st(
                    sentinel.arg1, sentinel.arg2)

    mock_isinstance.assert_called_once_with(mock_lookup.return_value,
                                            _Sequence)
    mock_callargs.assert_called_once_with(sentinel.arg1, sentinel.arg2)
    mock_lookup.assert_called_once_with(mock_callargs.return_value)
    mock_lookup.return_value.assert_called_once_with()
Example #17
0
def test_stub_switches_on_args():
    mock_fn = Mock()
    mock_fn.side_effect = stub((call(sentinel.argfoo), sentinel.foo),
                               (call(sentinel.whatever, sentinel.argbar), sentinel.bar),
                               (call(sentinel.whatever, sentinel.argbang), RuntimeError),
                               (call(sentinel.whatever, sentinel.argboom), RuntimeError(sentinel.boom)))

    assert mock_fn(sentinel.argfoo) == sentinel.foo

    assert mock_fn(sentinel.whatever, sentinel.argbar) == sentinel.bar

    with pytest.raises(RuntimeError):
        mock_fn(sentinel.whatever, sentinel.argbang)

    with pytest.raises(RuntimeError) as err:
        mock_fn(sentinel.whatever, sentinel.argboom)
    assert str(err.value) == str(sentinel.boom)
Example #18
0
def test_stub_switches_on_args():
    mock_fn = Mock()
    mock_fn.side_effect = stub(
        (call(sentinel.argfoo), sentinel.foo),
        (call(sentinel.whatever, sentinel.argbar), sentinel.bar),
        (call(sentinel.whatever, sentinel.argbang), RuntimeError), (call(
            sentinel.whatever, sentinel.argboom), RuntimeError(sentinel.boom)))

    assert mock_fn(sentinel.argfoo) == sentinel.foo

    assert mock_fn(sentinel.whatever, sentinel.argbar) == sentinel.bar

    with pytest.raises(RuntimeError):
        mock_fn(sentinel.whatever, sentinel.argbang)

    with pytest.raises(RuntimeError) as err:
        mock_fn(sentinel.whatever, sentinel.argboom)
    assert str(err.value) == str(sentinel.boom)
Example #19
0
def test_stub_switches_on_args():
    mock_fn = Mock()
    mock_fn.side_effect = stub((call(sentinel.argfoo), sentinel.foo),
                               (call(sentinel.whatever, sentinel.argbar), sentinel.bar),
                               (call(sentinel.whatever, sentinel.argbang), RuntimeError),
                               (call(sentinel.whatever, sentinel.argboom), RuntimeError(sentinel.boom)))

    assert mock_fn(sentinel.argfoo) == sentinel.foo

    assert mock_fn(sentinel.whatever, sentinel.argbar) == sentinel.bar

    with pytest.raises(RuntimeError):
        mock_fn(sentinel.whatever, sentinel.argbang)

    try:
        mock_fn(sentinel.whatever, sentinel.argboom)
    except RuntimeError as e:
        assert e.message == sentinel.boom
    else:
        assert False
Example #20
0
def test_stub_sequence_of_results_from_iterator():
    mock_fn = Mock()
    results = iter([sentinel.foo, sentinel.bar, RuntimeError, RuntimeError(sentinel.boom), sentinel.all_ok_now])
    mock_fn.side_effect = stub((call(sentinel.argfoo), seq(results)))

    assert mock_fn(sentinel.argfoo) == sentinel.foo

    assert mock_fn(sentinel.argfoo) == sentinel.bar

    with pytest.raises(RuntimeError):
        mock_fn(sentinel.argfoo)
        
    with pytest.raises(RuntimeError) as err:
        mock_fn(sentinel.argfoo)
    assert str(err.value) == str(sentinel.boom)

    assert mock_fn(sentinel.argfoo) == sentinel.all_ok_now

    with pytest.raises(StopIteration):
        mock_fn(sentinel.argfoo)
Example #21
0
def test_stub_sequence_of_results():
    mock_fn = Mock()
    mock_fn.side_effect = stub(
        (call(sentinel.argfoo), sentinel.foo, sentinel.bar, RuntimeError,
         RuntimeError(sentinel.boom), sentinel.all_ok_now))

    assert mock_fn(sentinel.argfoo) == sentinel.foo

    assert mock_fn(sentinel.argfoo) == sentinel.bar

    with pytest.raises(RuntimeError):
        mock_fn(sentinel.argfoo)

    with pytest.raises(RuntimeError) as err:
        mock_fn(sentinel.argfoo)
    assert str(err.value) == str(sentinel.boom)

    assert mock_fn(sentinel.argfoo) == sentinel.all_ok_now

    with pytest.raises(StopIteration):
        mock_fn(sentinel.argfoo)
Example #22
0
def test_stub_arg_matching():
    mock_fn = Mock()
    mock_fn.side_effect = stub((call(), sentinel.res0),
                               (call(sentinel.arg1), sentinel.res1),
                               (call(datetime(1978, 2, 2, 12, 34, 56)), sentinel.res2),
                               (call(Any()), sentinel.res3),
                               (call(x=sentinel.argx, y=sentinel.argy), sentinel.res4),
                               (call(x=sentinel.argx, y=datetime(1978, 2, 2, 12, 34, 56)), sentinel.res5),
                               (call(x=sentinel.argx, y=Any()), sentinel.res6))

    assert mock_fn() == sentinel.res0
    assert mock_fn(sentinel.arg1) == sentinel.res1
    assert mock_fn(Any()) == sentinel.res1
    assert mock_fn(Any(datetime)) == sentinel.res2
    assert mock_fn(datetime(1978, 2, 2, 12, 34, 56)) == sentinel.res2
    assert mock_fn(datetime(1978, 2, 2, 12, 45, 00)) == sentinel.res3
    assert mock_fn(sentinel.meh) == sentinel.res3
    assert mock_fn(x=sentinel.argx, y=sentinel.argy) == sentinel.res4
    assert mock_fn(x=sentinel.argx, y=datetime(1978, 2, 2, 12, 34, 56)) == sentinel.res5
    assert mock_fn(x=sentinel.argx, y=Any()) == sentinel.res4
    assert mock_fn(x=sentinel.argx, y=Any(datetime)) == sentinel.res5
    assert mock_fn(x=sentinel.argx, y=sentinel.meh) == sentinel.res6
Example #23
0
def test_stub_arg_matching():
    mock_fn = Mock()
    mock_fn.side_effect = stub(
        (call(), sentinel.res0), (call(sentinel.arg1), sentinel.res1),
        (call(datetime(1978, 2, 2, 12, 34, 56)), sentinel.res2),
        (call(Any()), sentinel.res3),
        (call(x=sentinel.argx, y=sentinel.argy), sentinel.res4),
        (call(x=sentinel.argx, y=datetime(1978, 2, 2, 12, 34, 56)),
         sentinel.res5), (call(x=sentinel.argx, y=Any()), sentinel.res6))

    assert mock_fn() == sentinel.res0
    assert mock_fn(sentinel.arg1) == sentinel.res1
    assert mock_fn(Any()) == sentinel.res1
    assert mock_fn(Any(datetime)) == sentinel.res2
    assert mock_fn(datetime(1978, 2, 2, 12, 34, 56)) == sentinel.res2
    assert mock_fn(datetime(1978, 2, 2, 12, 45, 00)) == sentinel.res3
    assert mock_fn(sentinel.meh) == sentinel.res3
    assert mock_fn(x=sentinel.argx, y=sentinel.argy) == sentinel.res4
    assert mock_fn(x=sentinel.argx, y=datetime(1978, 2, 2, 12, 34,
                                               56)) == sentinel.res5
    assert mock_fn(x=sentinel.argx, y=Any()) == sentinel.res4
    assert mock_fn(x=sentinel.argx, y=Any(datetime)) == sentinel.res5
    assert mock_fn(x=sentinel.argx, y=sentinel.meh) == sentinel.res6
Example #24
0
    def test_two_related_notifs_can_be_rolled_up(self):

        expected_payload = """<table>
            <tr style="background-color: #acf;">
              <th>When</th><th>Issues/Pull Requests &amp; Their Notifications</th>
            </tr>
            <tr style="">
              <td valign="top">Apr 02 2016<br/>03:14 AM</td>
              <td>
                <table style="border-top: none">
                  <tr>
                    <td style="border-bottom: 2px solid lightgrey;">
                      <a href="https://github.com/Homebrew/homebrew/pull/50441">Pull Request: [Homebrew/homebrew] ired 0.5.0 (#50441)</a>
                    </td>
                  </tr>
                  <tr>
                    <td style="font-weight: bold;">ppiper: Peter Piper (comment) Peter Piper picked a peck of pickled peppers....</td>
                  </tr>
                  <tr>
                    <td style="font-weight: bold;">dholm: David Holm (comment 60.0 mins earlier) [quoted block] @dunn Fixed....</td>
                  </tr>
                 </table>
              </td>
            </tr>
          </table>""".replace("\n          ", "\n")

        notification_store = {}

        final_notifs_store = NotifsStore()

        store_writer = Mock()
        store_writer.get_from_binary.side_effect = stub(
            (call('github-notifications'), notification_store),
            (call('most-recently-seen'), 0))
        store_writer.store_as_binary.side_effect = stub(
            (call('github-notifications', final_notifs_store), True),
            (call('most-recently-seen', 0), True))

        expected_message = "Subject: Watched Repositories Digest (2 new)\n" + MAIL_HDR + expected_payload \
                           + '\n\n-----NOTIFICATION_BOUNDARY-5678'

        digest_inbox_proxy = Mock()
        digest_inbox_proxy.delete_previous_message.side_effect = stub(
            (call(), True))
        digest_inbox_proxy.append.side_effect = stub(
            (call(expected_message), True))

        digesters = []
        digester = GithubNotificationDigester(
            store_writer, known_as="Our inhouse GH")  ## What we are testing
        digester.notification_boundary_rand = "-5678"  # no random number for the email's notification boundary
        digesters.append(digester)

        digestion_processor = DigestionProcessor(None, None, digesters, False,
                                                 "*****@*****.**", False,
                                                 "INBOX")

        unmatched_to_move = []
        to_delete_from_notification_folder = []

        digestion_processor.process_incoming_notification(
            1234, digesters, GITHUB_1, to_delete_from_notification_folder,
            unmatched_to_move, False)
        digestion_processor.process_incoming_notification(
            1235, digesters, GITHUB_2, to_delete_from_notification_folder,
            unmatched_to_move, False)

        digester.rewrite_digest_emails(digest_inbox_proxy,
                                       has_previous_message=True,
                                       previously_seen=False,
                                       sender_to_implicate="*****@*****.**")

        self.assertEqual(
            digest_inbox_proxy.mock_calls,
            [call.delete_previous_message(),
             call.append(expected_message)])

        self.assertEqual(store_writer.mock_calls, [
            call.get_from_binary('github-notifications'),
            call.get_from_binary('most-recently-seen'),
            call.store_as_binary(
                'github-notifications', {
                    'Homebrew/homebrew/pull/[email protected]': {
                        'subj': '[Homebrew/homebrew] ired 0.5.0 (#50441)',
                        'ts': {
                            1459577656: {
                                'msg': '[quoted block] @dunn Fixed....',
                                'diff': ' 60.0 mins earlier',
                                'what': 'comment',
                                'who': 'dholm: David Holm'
                            },
                            1459581256: {
                                'msg':
                                'Peter Piper picked a peck of pickled peppers....',
                                'diff': '',
                                'what': 'comment',
                                'who': 'ppiper: Peter Piper'
                            }
                        },
                        'mostRecent': 1459581256
                    }
                }),
            call.store_as_binary('most-recently-seen', 0)
        ])
        self.assertEqual(len(unmatched_to_move), 0)
        self.assertEqual(str(to_delete_from_notification_folder),
                         "[1234, 1235]")
        self.assertEqual(len(final_notifs_store.notifs), 1)
    def test_two_related_notifs_can_be_rolled_up(self):

        expected_payload = """<html>
          <head>
              <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
              <title>Atlassian HipChat</title>
          </head>
          <body style="box-sizing: border-box; height: 100%; width: 100%;">
          <table bgcolor="#f5f5f5" border="0" cellpadding="0" cellspacing="0" class="container wrapper_shrink"
                 style="_padding: 20px; padding: 3%;" width="640">
              <tr>
                  <td valign="top">
                      <table bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0" class="inner-container table_shrink"
                             id="email_content"
                             style="-khtml-border-radius: 6px; -moz-border-radius: 6px; -webkit-border-radius: 6px; border: 1px solid #dadada; border-radius: 6px; width: 100% !important; margin-top: 15px;"
                             width="600">
                          <tr>
                              <td class="td top-spacer"
                                  style="font-size: 15px; line-height: 4px; padding-left: 20px; padding-right: 10px !important;"
                                  valign="top">
                              </td>
                          </tr>
                          <tr>
                              <td>
                                  <div class="history_container history_email" id="chats" style="padding-right: 0px !important;">
                                      <div class="ecxhc-chat-from" style="margin-left: 150px;text-align:left;width:200px;padding:10px 0 10px 10px;">Direct Message</div>
          <div>
          <div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">
          <a name="a2f58d46-385e-4d7f-8bba-f8c0eb900d89" style="cursor: pointer; text-decoration: none;"></a>
          <div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>
          <div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">
          <div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">
                Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.<br><br>
          It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using \'Content here, content here\', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for \'lorem ipsum\' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).<br><br><br>
          Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.<br><br>
          The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.<br><br>
          There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don\'t look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn\'t anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.    </br></br></br></br></br></br></br></br></br></div>
          </div>
          <div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:49 PM</div>
          </div></div>
          <div class="ecxhc-chat-from" style="margin-left: 150px;text-align:left;width:200px;padding:10px 0 10px 10px;">Room: Hammant</div>
          <div>
          <div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">
          <a name="078141ad-2547-4cfc-a651-309c2a865692" style="cursor: pointer; text-decoration: none;"></a>
          <div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>
          <div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">
          <div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">
          <span class="atTag atTagMe" style="-moz-border-radius: 4px; -webkit-border-radius: 4px; background-color: #EFEFEF; border: 1px solid #4783BF; border-radius: 4px; display: inline-block; padding: 0 3px; background: #4282C1 url(/img/at_bg.png) repeat-x; color: #fff;">@test2</span> you suck    </div>
          </div>
          <div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:45 PM</div>
          </div></div>
                                  </div>
                              </td>
                          </tr>
                      </table>
                  </td>
              </tr>
          </table>
          </body>
          </html>""".replace("\n          ", "\n")

        notification_store = {}

        final_notifs_store = NotifsStore()

        store_writer = Mock()
        store_writer.get_from_binary.side_effect = stub(
            (call('hipchat-notifications'), notification_store),
            (call('most-recently-seen'), 0))
        store_writer.store_as_binary.side_effect = stub(
            (call('hipchat-notifications', final_notifs_store), True),
            (call('most-recently-seen', 0), True))

        expected_message = "Subject: Notification Digest: 2 new notification(s)\n" + MAIL_HDR + expected_payload + \
                           "\n\n-----NOTIFICATION_BOUNDARY-5678"

        digest_inbox_proxy = Mock()
        digest_inbox_proxy.delete_previous_message.side_effect = stub(
            (call(), True))
        digest_inbox_proxy.append.side_effect = stub(
            (call(expected_message), True))

        digesters = []
        digester = HipchatNotificationDigester(
            store_writer)  ## What we are testing
        digester.notification_boundary_rand = "-5678"  # no random number for the email's notification boundary
        digesters.append(digester)

        digestion_processor = DigestionProcessor(None, None, digesters, False,
                                                 "*****@*****.**", False,
                                                 "INBOX")

        unmatched_to_move = []
        to_delete_from_notification_folder = []

        digestion_processor.process_incoming_notification(
            1234, digesters, HIPCHAT_MENTION_IN_ROOM,
            to_delete_from_notification_folder, unmatched_to_move, False)
        digestion_processor.process_incoming_notification(
            1235, digesters, HIPCHAT_ONE_TO_ONE,
            to_delete_from_notification_folder, unmatched_to_move, False)

        digester.rewrite_digest_emails(digest_inbox_proxy,
                                       has_previous_message=True,
                                       previously_seen=False,
                                       sender_to_implicate="*****@*****.**")

        self.assertEqual(
            digest_inbox_proxy.mock_calls,
            [call.delete_previous_message(),
             call.append(expected_message)])

        calls = store_writer.mock_calls
        self.assertEqual(calls, [
            call.get_from_binary('hipchat-notifications'),
            call.get_from_binary('most-recently-seen'),
            call.store_as_binary(
                'hipchat-notifications', {
                    1459813540: {
                        'div':
                        '<div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">\n\
<a name="078141ad-2547-4cfc-a651-309c2a865692" style="cursor: pointer; text-decoration: none;"></a>\n\
<div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>\n\
<div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">\n\
<div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">\n\
<span class="atTag atTagMe" style="-moz-border-radius: 4px; -webkit-border-radius: 4px; background-color: #EFEFEF; border: 1px solid #4783BF; border-radius: 4px; display: inline-block; padding: 0 3px; background: #4282C1 url(/img/at_bg.png) repeat-x; color: #fff;">@test2</span> you suck    </div>\n\
</div>\n\
<div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:45 PM</div>\n\
</div>',
                        'room': 'Room: Hammant'
                    },
                    1459813751: {
                        'div':
                        '<div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">\n\
<a name="a2f58d46-385e-4d7f-8bba-f8c0eb900d89" style="cursor: pointer; text-decoration: none;"></a>\n\
<div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>\n\
<div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">\n\
<div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">\n\
      Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.<br><br>\n\
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using \'Content here, content here\', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for \'lorem ipsum\' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).<br><br><br>\n\
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.<br><br>\n\
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.<br><br>\n\
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don\'t look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn\'t anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.    </br></br></br></br></br></br></br></br></br></div>\n\
</div>\n\
<div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:49 PM</div>\n\
</div>',
                        'room': 'Direct Message'
                    }
                }),
            call.store_as_binary('most-recently-seen', 0)
        ])
        self.assertEqual(len(unmatched_to_move), 0)
        self.assertEqual(str(to_delete_from_notification_folder),
                         "[1234, 1235]")
        self.assertEqual(len(final_notifs_store.notifs), 2)
    def test_two_related_notifs_can_be_rolled_up(self):

        expected_payload = """<table>
            <tr style="background-color: #acf;">
              <th>When</th><th>Issues/Pull Requests &amp; Their Notifications</th>
            </tr>
            <tr style="">
              <td valign="top">Apr 02 2016<br/>03:14 AM</td>
              <td>
                <table style="border-top: none">
                  <tr>
                    <td style="border-bottom: 2px solid lightgrey;">
                      <a href="https://github.com/Homebrew/homebrew/pull/50441">Pull Request: [Homebrew/homebrew] ired 0.5.0 (#50441)</a>
                    </td>
                  </tr>
                  <tr>
                    <td style="font-weight: bold;">ppiper: Peter Piper (comment) Peter Piper picked a peck of pickled peppers....</td>
                  </tr>
                  <tr>
                    <td style="font-weight: bold;">dholm: David Holm (comment 60.0 mins earlier) [quoted block] @dunn Fixed....</td>
                  </tr>
                 </table>
              </td>
            </tr>
          </table>""".replace("\n          ","\n")

        notification_store = {}

        final_notifs_store = NotifsStore()

        store_writer = Mock()
        store_writer.get_from_binary.side_effect = stub(
            (call('github-notifications'), notification_store),
            (call('most-recently-seen'), 0)
        )
        store_writer.store_as_binary.side_effect = stub(
            (call('github-notifications', final_notifs_store), True),
            (call('most-recently-seen', 0), True)
        )

        expected_message = "Subject: Watched Repositories Digest (2 new)\n" + MAIL_HDR + expected_payload \
                           + '\n\n-----NOTIFICATION_BOUNDARY-5678'

        digest_inbox_proxy = Mock()
        digest_inbox_proxy.delete_previous_message.side_effect = stub((call(), True))
        digest_inbox_proxy.append.side_effect = stub((call(expected_message), True))

        digesters = []
        digester = GithubNotificationDigester(store_writer, known_as="Our inhouse GH")  ## What we are testing
        digester.notification_boundary_rand = "-5678"  # no random number for the email's notification boundary
        digesters.append(digester)

        digestion_processor = DigestionProcessor(None, None, digesters, False, "*****@*****.**", False, "INBOX")

        unmatched_to_move = []
        to_delete_from_notification_folder = []

        digestion_processor.process_incoming_notification(1234, digesters, GITHUB_1, to_delete_from_notification_folder, unmatched_to_move, False)
        digestion_processor.process_incoming_notification(1235, digesters, GITHUB_2, to_delete_from_notification_folder, unmatched_to_move, False)

        digester.rewrite_digest_emails(digest_inbox_proxy, has_previous_message=True,
                                       previously_seen=False, sender_to_implicate="*****@*****.**")

        self.assertEqual(digest_inbox_proxy.mock_calls, [call.delete_previous_message(), call.append(expected_message)])

        self.assertEqual(store_writer.mock_calls, [
            call.get_from_binary('github-notifications'),
            call.get_from_binary('most-recently-seen'),
            call.store_as_binary('github-notifications', {
                'Homebrew/homebrew/pull/[email protected]': {
                    'subj': '[Homebrew/homebrew] ired 0.5.0 (#50441)',
                    'ts': {
                        1459577656: {
                        'msg': '[quoted block] @dunn Fixed....',
                        'diff': ' 60.0 mins earlier',
                        'what': 'comment',
                        'who': 'dholm: David Holm'
                    },
                        1459581256: {
                            'msg': 'Peter Piper picked a peck of pickled peppers....',
                            'diff': '',
                            'what': 'comment',
                            'who': 'ppiper: Peter Piper'
                        }
                    },
                    'mostRecent': 1459581256
                }
            }),
            call.store_as_binary('most-recently-seen', 0)])
        self.assertEqual(len(unmatched_to_move), 0)
        self.assertEqual(str(to_delete_from_notification_folder), "[1234, 1235]")
        self.assertEqual(len(final_notifs_store.notifs), 1)
Example #27
0
def test_lookup():
    test_data = stub((sentinel.keya, sentinel.vala), (sentinel.keyb, sentinel.valb))

    assert sentinel.vala == test_data._lookup(sentinel.keya)

    assert sentinel.valb == test_data._lookup(sentinel.keyb)
Example #28
0
def test_error_on_missed_lookup_on_empty_stub():
    with pytest.raises(UnexpectedStubCall) as err:
        stub()._lookup(sentinel.keya)

    assert str(err.value) == "Unexpected call of an unconfigured stub"
    def test_two_related_notifs_can_be_rolled_up(self):

        expected_payload = """<html>
          <head>
              <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
              <title>Atlassian HipChat</title>
          </head>
          <body style="box-sizing: border-box; height: 100%; width: 100%;">
          <table bgcolor="#f5f5f5" border="0" cellpadding="0" cellspacing="0" class="container wrapper_shrink"
                 style="_padding: 20px; padding: 3%;" width="640">
              <tr>
                  <td valign="top">
                      <table bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0" class="inner-container table_shrink"
                             id="email_content"
                             style="-khtml-border-radius: 6px; -moz-border-radius: 6px; -webkit-border-radius: 6px; border: 1px solid #dadada; border-radius: 6px; width: 100% !important; margin-top: 15px;"
                             width="600">
                          <tr>
                              <td class="td top-spacer"
                                  style="font-size: 15px; line-height: 4px; padding-left: 20px; padding-right: 10px !important;"
                                  valign="top">
                              </td>
                          </tr>
                          <tr>
                              <td>
                                  <div class="history_container history_email" id="chats" style="padding-right: 0px !important;">
                                      <div class="ecxhc-chat-from" style="margin-left: 150px;text-align:left;width:200px;padding:10px 0 10px 10px;">Direct Message</div>
          <div>
          <div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">
          <a name="a2f58d46-385e-4d7f-8bba-f8c0eb900d89" style="cursor: pointer; text-decoration: none;"></a>
          <div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>
          <div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">
          <div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">
                Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.<br><br>
          It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using \'Content here, content here\', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for \'lorem ipsum\' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).<br><br><br>
          Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.<br><br>
          The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.<br><br>
          There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don\'t look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn\'t anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.    </br></br></br></br></br></br></br></br></br></div>
          </div>
          <div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:49 PM</div>
          </div></div>
          <div class="ecxhc-chat-from" style="margin-left: 150px;text-align:left;width:200px;padding:10px 0 10px 10px;">Room: Hammant</div>
          <div>
          <div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">
          <a name="078141ad-2547-4cfc-a651-309c2a865692" style="cursor: pointer; text-decoration: none;"></a>
          <div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>
          <div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">
          <div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">
          <span class="atTag atTagMe" style="-moz-border-radius: 4px; -webkit-border-radius: 4px; background-color: #EFEFEF; border: 1px solid #4783BF; border-radius: 4px; display: inline-block; padding: 0 3px; background: #4282C1 url(/img/at_bg.png) repeat-x; color: #fff;">@test2</span> you suck    </div>
          </div>
          <div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:45 PM</div>
          </div></div>
                                  </div>
                              </td>
                          </tr>
                      </table>
                  </td>
              </tr>
          </table>
          </body>
          </html>""".replace("\n          ","\n")

        notification_store = {}

        final_notifs_store = NotifsStore()

        store_writer = Mock()
        store_writer.get_from_binary.side_effect = stub(
            (call('hipchat-notifications'), notification_store),
            (call('most-recently-seen'), 0)
        )
        store_writer.store_as_binary.side_effect = stub(
            (call('hipchat-notifications', final_notifs_store), True),
            (call('most-recently-seen', 0), True)
        )

        expected_message = "Subject: Notification Digest: 2 new notification(s)\n" + MAIL_HDR + expected_payload + \
                           "\n\n-----NOTIFICATION_BOUNDARY-5678"

        digest_inbox_proxy = Mock()
        digest_inbox_proxy.delete_previous_message.side_effect = stub((call(), True))
        digest_inbox_proxy.append.side_effect = stub((call(expected_message), True))

        digesters = []
        digester = HipchatNotificationDigester(store_writer)  ## What we are testing
        digester.notification_boundary_rand = "-5678"  # no random number for the email's notification boundary
        digesters.append(digester)

        digestion_processor = DigestionProcessor(None, None, digesters, False, "*****@*****.**", False, "INBOX")

        unmatched_to_move = []
        to_delete_from_notification_folder = []

        digestion_processor.process_incoming_notification(1234, digesters, HIPCHAT_MENTION_IN_ROOM, to_delete_from_notification_folder, unmatched_to_move, False)
        digestion_processor.process_incoming_notification(1235, digesters, HIPCHAT_ONE_TO_ONE, to_delete_from_notification_folder, unmatched_to_move, False)

        digester.rewrite_digest_emails(digest_inbox_proxy, has_previous_message=True,
                                       previously_seen=False, sender_to_implicate="*****@*****.**")

        self.assertEquals(digest_inbox_proxy.mock_calls, [call.delete_previous_message(), call.append(expected_message)])

        calls = store_writer.mock_calls
        self.assertEquals(calls, [
            call.get_from_binary('hipchat-notifications'),
            call.get_from_binary('most-recently-seen'),
            call.store_as_binary('hipchat-notifications', {1459813540: {u'div': '<div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">\n\
<a name="078141ad-2547-4cfc-a651-309c2a865692" style="cursor: pointer; text-decoration: none;"></a>\n\
<div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>\n\
<div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">\n\
<div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">\n\
<span class="atTag atTagMe" style="-moz-border-radius: 4px; -webkit-border-radius: 4px; background-color: #EFEFEF; border: 1px solid #4783BF; border-radius: 4px; display: inline-block; padding: 0 3px; background: #4282C1 url(/img/at_bg.png) repeat-x; color: #fff;">@test2</span> you suck    </div>\n\
</div>\n\
<div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:45 PM</div>\n\
</div>', u'room': u'Room: Hammant'}, 1459813751: {u'div': '<div class="hc-chat-row hc-msg-nocolor" style="border-bottom: 1px solid #efefef; box-sizing: border-box; display: table; padding: 0; width: 600px !important; max-width: 600px !important;">\n\
<a name="a2f58d46-385e-4d7f-8bba-f8c0eb900d89" style="cursor: pointer; text-decoration: none;"></a>\n\
<div class="hc-chat-from" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; color: #888; max-width: 100px; overflow: hidden; text-align: right; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; width: 100px; padding: 10px 0 10px 10px;">Paul Hammant</div>\n\
<div class="hc-chat-msg" style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px; line-height: 20px; padding: 10px; flex: 1;">\n\
<div style="display: table-cell; font-size: 13px; margin-bottom: 15px; margin-top: 15px;">\n\
      Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.<br><br>\n\
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using \'Content here, content here\', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for \'lorem ipsum\' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).<br><br><br>\n\
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.<br><br>\n\
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.<br><br>\n\
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don\'t look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn\'t anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.    </br></br></br></br></br></br></br></br></br></div>\n\
</div>\n\
<div class="hc-chat-time" style="display: table-cell; font-size: 10px !important; margin-bottom: 15px; margin-top: 15px; color: #888; text-align: right; vertical-align: top; white-space: nowrap; width: 40px; padding: 10px 10px 10px 0;">7:49 PM</div>\n\
</div>', u'room': u'Direct Message'}} ),
            call.store_as_binary('most-recently-seen', 0)])
        self.assertEquals(len(unmatched_to_move), 0)
        self.assertEquals(str(to_delete_from_notification_folder), "[1234, 1235]")
        self.assertEquals(len(final_notifs_store.notifs), 2)
Example #30
0
def test_error_on_missed_lookup_on_empty_stub():
    with pytest.raises(UnexpectedStubCall) as err:
        stub()._lookup(sentinel.keya)

    assert str(err.value) == "Unexpected call of an unconfigured stub"