# -*- coding: utf-8 -*-
"""Copyright 2017 Stephen Rigden
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 3 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, see <http://www.gnu.org/licenses/>.
"""
import decimal
import unittest
from unittest import mock

import chrono

from error import PigjarInvariantError
import event
from test.mixins import MockMixin


class Event1SpendTrue(MockMixin, unittest.TestCase):
    # noinspection PyMissingOrEmptyDocstring
    def setUp(self):
        self.evt = event.Event1('10/19/2017', True, amount=decimal.Decimal('42.42'), offset=3,
                                name='Test Event', desc='Test event description.',)


class Event1MagicSpendTrue(Event1SpendTrue):
    def test_init(self):
        match_list = [(self.evt.name, 'Test Event'),
                      (self.evt.desc, 'Test event description.'),
                      (self.evt.date, '10/19/2017'),
                      (self.evt.amount, decimal.Decimal('42.42')),
                      (self.evt.offset, 3),
                      (self.evt.spend, True),
                      (self.evt.svg, False),
                      (self.evt.acc, None),
                      (self.evt.payee, None),
                      (self.evt.memo, None), ]
        for expected, actual in match_list:
            with self.subTest(expected=expected, actual=actual):
                self.assertEqual(expected, actual)

    def test_str(self):
        expected = "Event1 <id={}> '{}' on {} for {}.".format(id(self.evt), self.evt.name,
                                                              self.evt.date, self.evt.amount)
        self.assertEqual(expected, str(self.evt))

    def test_repr(self):
        expected = ("Event1<id={}>('{}', amount={}, offset={}, name='{}', desc='{}', "
                    "spend={}, svg={}, account='{!r}', payee='{!r}')"
                    "".format(id(self.evt), self.evt.date,
                              self.evt.amount, self.evt.offset, self.evt.name,
                              self.evt.desc, self.evt.spend, self.evt.svg, self.evt.acc,
                              self.evt.payee))
        self.assertEqual(expected, repr(self.evt))

    def test_eq(self):
        evt2 = event.Event1('10/19/2017', amount=decimal.Decimal('42.42'), offset=3,
                            name='Test Event', desc='Test event description.',
                            spend=True, )
        self.assertEqual(True, evt2 == self.evt)

    def test_eq_returns_false(self):
        self.assertEqual(False, self.evt == object())

    def test_lt(self):
        evt2 = event.Event1('11/19/2017', amount=decimal.Decimal('43.43'),
                            name='Test Event 2', desc='Test event 2 description.',
                            spend=True, )
        self.assertTrue(self.evt < evt2)


class Event1FromJSON(Event1SpendTrue):
    # noinspection PyMissingOrEmptyDocstring
    def setUp(self):
        super().setUp()
        self.mock_invariants = self.mock_patch_object(event.Event1, '_Event1__invariants')
        self.namespace = dict(date=chrono.Date('4/1/2017'), spend=True,
                              amount=decimal.Decimal('42.42'), name='Test',
                              desc='Test JSON constructor')
        self.result = self.evt.from_json(**self.namespace)

    def test_json_constructor(self):
        evt2 = event.Event1(chrono.Date('4/1/2017'), True, decimal.Decimal('42.42'),
                            name='Test', desc='Test JSON constructor')
        self.assertEqual(evt2, self.result)

    def test_invariants_called(self):
        self.mock_invariants.assert_called_with()


class Event1LogAndRaiseInvariantError(Event1SpendTrue):
    def test_call_logs_and_raises_pigjar_invariant_error(self):
        mock_logging = self.mock_patch('event.logging')
        msg = 'Test message'
        with self.assertRaises(PigjarInvariantError) as cm:
            self.evt.log_and_raise_invariant_error(msg)
        mock_logging.critical.assert_called_with(msg=msg)
        self.assertEqual(msg, cm.exception.args[0])


class Event1Invariants(Event1SpendTrue):
    def test_spend_and_acc_calls_log_and_raise_invariant_error(self):
        self.evt.spend = True
        self.evt.acc = 'mock account'
        msg = "Account must not be present in spending event '{}'.".format(self.evt)
        self.log_and_raise(msg)

    def test_spend_and_payee_calls_log_and_raise_invariant_error(self):
        self.evt.spend = True
        self.evt.payee = 'mock payee'
        msg = "Payee must not be present in spending event '{}'.".format(self.evt)
        self.log_and_raise(msg)

    def test_spend_and_memo_calls_log_and_raise_invariant_error(self):
        self.evt.spend = True
        self.evt.memo = 'mock memo'
        msg = "Memo must not be present in spending event '{}'.".format(self.evt)
        self.log_and_raise(msg)

    def test_no_spend_and_no_acc_calls_log_and_raise_invariant_error(self):
        self.evt.spend = False
        self.evt.acc = None
        msg = "Account must be present in non-spending event '{}'.".format(self.evt)
        self.log_and_raise(msg)

    def test_no_spend_and_no_payee_calls_log_and_raise_invariant_error(self):
        self.evt.spend = False
        self.evt.acc = 'mock account'
        self.evt.payee = None
        msg = "Payee must be present in non-spending event '{}'.".format(self.evt)
        self.log_and_raise(msg)

    def test_svg_and_no_spend_calls_log_and_raise_invariant_error(self):
        self.evt.svg = True
        self.evt.spend = False
        self.evt.acc = 'mock account'
        self.evt.payee = 'mock payee'
        msg = ("Planned spending must be true if saving is true in event '{}'."
               "".format(self.evt))
        self.log_and_raise(msg)

    # noinspection PyMissingOrEmptyDocstring
    def log_and_raise(self, msg):
        mock_logging = self.mock_patch('event.logging')
        with self.assertRaises(PigjarInvariantError) as cm:
            self.evt.invariants()
        mock_logging.critical.assert_called_with(msg=msg)
        self.assertEqual(msg, cm.exception.args[0])


class Event1JSONEncode(Event1SpendTrue):
    def test_json_encoding(self):
        expected = dict(__class__='event.Event1',
                        __id__=id(self.evt),
                        __args__=[],
                        __kw__=self.evt.__dict__)
        self.assertEqual(expected, self.evt.json_encode())


class Event1Duedate(Event1SpendTrue):
    def test_return_value_with_offset(self):
        expected = chrono.Date(year=2017, month=10, day=16)
        self.assertEqual(expected, self.evt.duedate)

    def test_return_value_without_offset(self):
        self.evt.offset = 0
        expected = chrono.Date(year=2017, month=10, day=19)
        self.assertEqual(expected, self.evt.duedate)


class Event1SpendFalse(MockMixin, unittest.TestCase):
    # noinspection PyMissingOrEmptyDocstring
    def setUp(self):
        self.mock_logging = self.mock_patch('event.logging')
        self.acc = self.mock_patch('account.Account')
        self.payee = self.mock_patch('payee.Payee')
        self.evt = event.Event1('10/19/2017', amount=decimal.Decimal('42.42'),
                                name='Test Event', desc='Test event description.',
                                spend=False, acc=self.acc, payee=self.payee,
                                memo='Parrot grooming')


class Event1MagicSpendFalse(Event1SpendFalse):
    def test_init(self):
        match_list = [(self.evt.name, 'Test Event', 'name'),
                      (self.evt.desc, 'Test event description.', 'desc'),
                      (self.evt.date, '10/19/2017', 'date'),
                      (self.evt.amount, decimal.Decimal('42.42'), 'amount'),
                      (self.evt.offset, 0, 'offset'),
                      (self.evt.spend, False, 'spend'),
                      (self.evt.svg, False, 'svg'),
                      (self.evt.acc, self.acc, 'acc'),
                      (self.evt.payee, self.payee, 'payee'),
                      (self.evt.memo, 'Parrot grooming', 'memo'), ]
        for expected, actual, msg in match_list:
            with self.subTest(expected=expected, actual=actual, msg=msg):
                self.assertEqual(expected, actual, msg=msg)

    def test_repr(self):
        expected = ("Event1<id={}>('{}', amount={}, offset={}, name='{}', desc='{}', "
                    "spend={}, svg={}, account='{!r}', payee='{!r}')"
                    "".format(id(self.evt), self.evt.date,
                              self.evt.amount, self.evt.offset, self.evt.name,
                              self.evt.desc, self.evt.spend, self.evt.svg,
                              self.evt.acc,
                              self.evt.payee))
        self.assertEqual(expected, repr(self.evt))

    def test_lt_raises_type_error(self):
        with self.assertRaises(TypeError) as cm:
            _ = self.evt > self.acc
        msg = "Expected an Event1 object with attribute 'date', got {}.".format(self.acc)
        self.mock_logging.critical.assert_called_with(msg=msg)
        self.assertEqual(msg, cm.exception.args[0])


class Eventm(MockMixin, unittest.TestCase):
    # noinspection PyMissingOrEmptyDocstring
    def setUp(self):
        self.mock_support = self.mock_patch('event.support')
        self.mock_support.days_of_week.return_value = (
            None, 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
            'Friday', 'Saturday', 'Sunday',)
        self.mock_support.weeks_of_month.return_value = (
            None, '1st', '2nd', '3rd', '4th', 'last')
        self.mock_support.months_of_year.return_value = (
            None, 'January', 'February', 'March', 'April', 'May', 'June', 'July',
            'August', 'September', 'October', 'November', 'December')
        self.evt = event.Eventm(date=chrono.Date('9/22/2017'),
                                inval=14, inval_type='day', spend=True,
                                amount=decimal.Decimal('42.42'), offset=3, name='Test Event',
                                desc='Test recurring event', )


class EventmMagic(Eventm):
    def test_init(self):
        match_list = [(self.evt.name, 'Test Event'),
                      (self.evt.desc, 'Test recurring event'),
                      (self.evt.date, '9/22/2017'),
                      (self.evt.amount, decimal.Decimal('42.42')),
                      (self.evt.offset, 3),
                      (self.evt.spend, True),
                      (self.evt.svg, False),
                      (self.evt.acc, None),
                      (self.evt.payee, None),
                      (self.evt.memo, None),
                      (self.evt.inval, 14),
                      (self.evt.inval_type, 'day'),
                      (self.evt.endrep, False),
                      (self.evt.endrep_count, 0),
                      (self.evt.endrep_date, None),
                      (self.evt.dom, None),
                      (self.evt.dbem, None),
                      (self.evt.wom, None),
                      (self.evt.dow, None),
                      (self.evt.moy, None), ]
        for expected, actual in match_list:
            with self.subTest(expected=expected, actual=actual):
                self.assertEqual(expected, actual)

    def test_init_with_endrep(self):
        self.evt = event.Eventm(date=chrono.Date('10/22/2017'), inval=14, inval_type='day',
                                amount=decimal.Decimal('42.42'), offset=3, name='Test Event',
                                desc='Test recurring event', spend=True,
                                endrep=True,
                                endrep_date=chrono.Date('10/22/2018'))
        match_list = [(self.evt.endrep, True), (self.evt.endrep_count, 0),
                      (self.evt.endrep_date, chrono.Date('10/22/2018')), ]
        for expected, actual in match_list:
            with self.subTest(expected=expected, actual=actual):
                self.assertEqual(expected, actual)

    def test_init_with_endrep_count_gt_zero(self):
        self.evt = event.Eventm(date=chrono.Date('10/22/2017'), inval=14, inval_type='day',
                                amount=decimal.Decimal('42.42'), offset=3, name='Test Event',
                                desc='Test recurring event', spend=True,
                                endrep=True,
                                endrep_count=42,
                                endrep_date=chrono.Date('10/22/2018'))
        match_list = [(self.evt.endrep, True), (self.evt.endrep_count, 42),
                      (self.evt.endrep_date, None), ]
        for expected, actual in match_list:
            with self.subTest(expected=expected, actual=actual):
                self.assertEqual(expected, actual)

    def test_str(self):
        expected = ("Multiple event <id={}> '{}' for {} next due on {} "
                    "and then every {} {}(s)."
                    "".format(id(self.evt), self.evt.name, self.evt.amount, self.evt.date,
                              self.evt.inval, self.evt.inval_type))
        self.assertEqual(expected, str(self.evt))

    def test_repr(self):
        expected = (("{}('{}', {}, {}, amount={}, name={}, desc={}, "
                     "offset={}, spend={}, svg={}, acc={}, endrep={}, "
                     "endrep_count={}, endrep_date={}, dow={}, dom={}, "
                     "dbem={}, wom={}, moy={})").format
                    (self.evt.__class__.__name__, self.evt.date, self.evt.inval,
                     self.evt.inval_type, self.evt.amount, self.evt.name, self.evt.desc,
                     self.evt.offset, self.evt.spend, self.evt.svg, self.evt.acc,
                     self.evt.endrep, self.evt.endrep_count, self.evt.endrep_date,
                     self.evt.dow, self.evt.dom, self.evt.dbem, self.evt.wom, self.evt.moy))
        self.assertEqual(expected, repr(self.evt))


class EventmFromJSON(Eventm):
    # noinspection PyMissingOrEmptyDocstring
    def setUp(self):
        super().setUp()
        self.mock_invariants_1 = self.mock_patch_object(event.Event1, '_Event1__invariants')
        self.mock_invariants_m = self.mock_patch_object(event.Eventm, '_Eventm__invariants')
        self.namespace = dict(date=chrono.Date('4/1/2017'), spend=True,
                              amount=decimal.Decimal('42.42'), name='Test',
                              desc='Test JSON constructor',
                              inval=42, inval_type='day')
        self.result = self.evt.from_json(**self.namespace)

    def test_json_constructor(self):
        evt2 = event.Eventm(chrono.Date('4/1/2017'), spend=True, inval=42, inval_type='day',
                            amount=decimal.Decimal('42.42'),
                            name='Test', desc='Test JSON constructor',)
        self.assertEqual(evt2, self.result)

    def test_invariants_called(self):
        self.mock_invariants_m.assert_called_with()


class EventmInvariants(Eventm):
    # noinspection PyMissingOrEmptyDocstring
    def setUp(self):
        super().setUp()
        self.mock_log_and_raise = self.mock_patch_object(
            event.Event1, 'log_and_raise_invariant_error')
        self.evt.inval_type = 'year'

    def test_super_called(self):
        with mock.patch('event.super') as mock_super:
            self.evt.invariants()
        mock_super().invariants.assert_called_with()

    def test_invalid_inval_type_logs_and_raises_value_error(self):
        self.evt.inval_type = 'garbage'
        mock_logging = self.mock_patch('event.logging')
        with mock.patch('event.super'):
            with self.assertRaises(ValueError) as cm:
                self.evt.invariants()
        msg = ("Unexpected interval period of '{}' in Event '{}'.".
               format(self.evt.inval_type, self.evt))
        mock_logging.critical.assert_called_with(msg=msg)
        self.assertEqual(msg, cm.exception.args[0])

    def test_invalid_dow_calls_log_and_raise_invariant_error(self):
        self.evt.dow = 'garbage'
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "Invalid day of week ({}) in Event '{}'.".format(self.evt.dow, self.evt))

    def test_invalid_wom_calls_log_and_raise_invariant_error(self):
        self.evt.wom = 'garbage'
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "Invalid week of month ({}) in Event '{}'.".format(self.evt.wom, self.evt))

    def test_invalid_moy_calls_log_and_raise_invariant_error(self):
        self.evt.inval_type = 'year'
        self.evt.moy = 'garbage'
        self.evt.dom = 42
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "Invalid month of year ({}) in Event '{}'.".format(self.evt.moy, self.evt))

    def test_invalid_endrep_combination_calls_log_and_raise_invariant_error(self):
        self.evt.endrep = True
        self.evt.endrep_date = True
        self.evt.endrep_count = True
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "Either an end date or the number of remaining events can be present but "
            "not both. Event '{}'.".format(self.evt))

    def test_invalid_dom_and_dbem_combination_calls_log_and_raise_invariant_error(self):
        self.evt.dom = True
        self.evt.dbem = True
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "Either days of month or days before end of month can be present but "
            "not both. Event '{}'.".format(self.evt))

    def test_invalid_moy_and_inval_type_combination_calls_log_and_raise_invariant_error(self):
        self.evt.moy = True
        self.evt.inval_type = 'week'
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "'{}' is not a valid '{}' interval in Event '{}'."
            "".format(self.evt.moy, self.evt.inval_type, self.evt))

    def test_invalid_wom_and_inval_type_combination_calls_log_and_raise_invariant_error(self):
        self.evt.wom = True
        self.evt.inval_type = 'week'
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "'{}' is not a valid '{}' interval in Event '{}'."
            "".format(self.evt.wom, self.evt.inval_type, self.evt))

    def test_invalid_dom_and_inval_type_combination_calls_log_and_raise_invariant_error(self):
        self.evt.dom = True
        self.evt.inval_type = 'day'
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "'{}' is not a valid '{}' interval in Event '{}'."
            "".format(self.evt.dom, self.evt.inval_type, self.evt))

    def test_invalid_dbem_and_inval_type_combination_calls_log_and_raise_invariant_error(self):
        self.evt.dbem = True
        self.evt.inval_type = 'day'
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "'{}' is not a valid '{}' interval in Event '{}'."
            "".format(self.evt.dbem, self.evt.inval_type, self.evt))

    def test_invalid_dow_and_inval_type_combination_calls_log_and_raise_invariant_error(self):
        self.evt.dow = True
        self.evt.inval_type = 'day'
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "'{}' is not a valid 'day' interval in Event '{}'."
            "".format(self.evt.dow, self.evt))

    def test_invalid_dow_wom_and_month_combination_calls_log_and_raise_invariant_error(self):
        self.evt.inval_type = 'month'
        self.evt.dow = True
        self.evt.wom = False
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "For 'month' interval both week of month and day of week must be present or "
            "neither must be present in Event '{}'."
            "".format(self.evt))

    def test_missing_dow_with_moy_and_wom_calls_log_and_raise_invariant_error(self):
        self.evt.inval_type = 'year'
        self.evt.moy = True
        self.evt.wom = True
        self.evt.dow = False
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "For 'year' interval with moy and wom, dow must be present in Event '{}'."
            "".format(self.evt))

    def test_bad_dom_dbem_and_wom_combination_calls_log_and_raise_invariant_error(self):
        self.evt.inval_type = 'year'
        self.evt.moy = True
        self.evt.dom = True
        self.evt.dbem = True
        self.evt.wom = True
        with mock.patch('event.super'):
            self.evt.invariants()
        self.mock_log_and_raise.assert_called_with(
            "For 'year' interval with moy, only one of dom '{}', dbem '{}', "
            "or wom '{}' must be specified in Event '{}'."
            "".format(self.evt.dom, self.evt.dbem, self.evt.wom, self.evt))


class EventmJSONEncode(Eventm):
    def test_json_encoding(self):
        expected = dict(__class__='event.Eventm',
                        __id__=id(self.evt),
                        __args__=[],
                        __kw__=self.evt.__dict__)
        self.assertEqual(expected, self.evt.json_encode())


class EventmAdvanceDate(Eventm):
    # noinspection PyMissingOrEmptyDocstring
    def setUp(self):
        super().setUp()
        self.mock_advance_dwmy = self.mock_patch_object(event.Eventm, 'advance_dwmy')
        self.mock_invariants = self.mock_patch_object(event.Eventm,
                                                      '_Eventm__invariants')

    def test_advance_dwmy_called(self):
        self.evt.endrep = False
        self.evt.advance_date()
        self.mock_advance_dwmy.assert_called_with()

    def test_invariants_called(self):
        self.evt.endrep = False
        self.evt.advance_date()
        self.mock_invariants.assert_called_with()

    def test_endrep_count_decremented(self):
        self.evt.endrep = True
        self.evt.endrep_date = None
        self.evt.endrep_count = 42
        self.evt.advance_date()
        self.assertEqual(41, self.evt.endrep_count)

    def test_endrep_count_exceeded_raises_stop_iteration(self):
        self.evt.endrep = True
        self.evt.endrep_date = None
        self.evt.endrep_count = 0
        with self.assertRaises(StopIteration):
            self.evt.advance_date()

    def test_endrep_date_updated(self):
        # noinspection PyMissingOrEmptyDocstring
        def change_date():
            self.evt.date = chrono.Date('10/26/2017')

        self.evt.advance_dwmy = change_date
        self.evt.endrep = True
        self.evt.endrep_date = chrono.Date('11/22/2017')
        self.evt.advance_date()
        self.assertEqual(chrono.Date('10/26/2017'), self.evt.date)

    def test_endrep_date_exceeded_raises_stop_iteration(self):
        self.evt.endrep = True
        self.evt.endrep_date = chrono.Date('8/22/2017')
        with self.assertRaises(StopIteration):
            self.evt.advance_date()

    def test_endrep_date_exceeded_leaves_date_unchanged(self):
        # noinspection PyMissingOrEmptyDocstring
        def change_date():
            self.evt.date = chrono.Date('11/22/2017')

        self.evt.advance_dwmy = change_date
        self.evt.endrep = True
        self.evt.endrep_date = chrono.Date('10/22/2017')
        with self.assertRaises(StopIteration):
            self.evt.advance_date()
        self.assertEqual(chrono.Date('9/22/2017'), self.evt.date)


class EventmAdvanceDWMY(Eventm):
    def test_inval_type_day(self):
        self.evt.inval_type = 'day'
        self.evt.inval = 42
        self.evt.advance_dwmy()
        self.assertEqual(chrono.Date('11/3/2017'), self.evt.date)

    def test_inval_type_week(self):
        self.evt.inval_type = 'week'
        self.evt.dow = None
        self.evt.inval = 6
        self.evt.advance_dwmy()
        self.assertEqual(chrono.Date('11/3/2017'), self.evt.date)

    def test_inval_type_week_with_dow(self):
        self.evt.inval_type = 'week'
        self.evt.dow = 'Wednesday'
        self.evt.inval = 7
        self.evt.advance_dwmy()
        self.assertEqual(chrono.Date('11/8/2017'), self.evt.date)

    def test_inval_type_month(self):
        self.evt.inval_type = 'month'
        self.evt.dom = None
        self.evt.dbem = None
        self.evt.wom = None
        self.evt.inval = 2
        self.evt.advance_dwmy()
        self.assertEqual(chrono.Date('11/22/2017'), self.evt.date)

    # noinspection PyMissingOrEmptyDocstring
    def inval_type_month_with_dom_dbem_or_wom(self):
        self.mock_fixed_day = self.mock_patch_object(event.Eventm, 'fixed_day')
        self.evt.inval_type = 'month'
        self.evt.dom = 5
        self.evt.dbem = None
        self.evt.wom = None
        self.evt.inval = 2
        self.evt.advance_dwmy()

    def test_inval_type_month_with_dom_dbem_or_wom_calls_fixed_day(self):
        self.inval_type_month_with_dom_dbem_or_wom()
        self.mock_fixed_day.assert_called_with()

    def test_inval_type_month_with_dom_dbem_or_wom(self):
        self.inval_type_month_with_dom_dbem_or_wom()
        self.assertEqual(chrono.Date('11/1/2017'), self.evt.date)

    def test_inval_type_year(self):
        self.evt.inval_type = 'year'
        self.evt.moy = None
        self.evt.inval = 2
        self.evt.advance_dwmy()
        self.assertEqual(chrono.Date('9/22/2019'), self.evt.date)

    # noinspection PyMissingOrEmptyDocstring
    def inval_type_year_with_moy(self):
        self.mock_fixed_day = self.mock_patch_object(event.Eventm, 'fixed_day')
        self.evt.inval_type = 'year'
        self.evt.moy = 'March'
        self.evt.inval = 2
        self.evt.advance_dwmy()

    def test_inval_type_year_with_moy_calls_fixed_day(self):
        self.inval_type_year_with_moy()
        self.mock_fixed_day.assert_called_with()

    def test_inval_type_year_with_moy(self):
        self.inval_type_year_with_moy()
        self.assertEqual(chrono.Date('3/1/2019'), self.evt.date)

    def test_unhandled_inval_type_logs_and_raises_pigjar_branch_error(self):
        mock_logging = self.mock_patch('event.logging')
        self.evt.inval_type = 'not a valid interval test'
        with self.assertRaises(ValueError) as cm:
            self.evt.advance_dwmy()
        msg = ("Event interval of '{}' is not day, week, month, or year".
               format(self.evt.inval_type))
        mock_logging.critical.assert_called_with(msg=msg)
        self.assertEqual(msg, cm.exception.args[0])


class EventmFixedDay(Eventm):
    def test_dom(self):
        match_list = [(5, 5), (42, 30), ]
        for dom, expected in match_list:
            with self.subTest(dom=dom, expected=expected):
                self.evt.dom = dom
                self.evt.fixed_day()
                self.assertEqual(expected, self.evt.date.day)

    def test_dbem(self):
        match_list = [(29, 2), (31, 1), (1, 30), (2, 29), (42, 1), ]
        for dbem, expected in match_list:
            with self.subTest(dbem=dbem, expected=expected):
                self.evt.dbem = dbem
                self.evt.fixed_day()
                self.assertEqual(expected, self.evt.date.day)

    def test_wom(self):
        match_list = [
            ('1st', None, 4),
            ('1st', 'Monday', 4),
            ('1st', 'Tuesday', 5),
            ('1st', 'Wednesday', 6),
            ('1st', 'Thursday', 7),
            ('1st', 'Friday', 1),
            ('1st', 'Saturday', 2),
            ('1st', 'Sunday', 3),

            ('2nd', None, 11),
            ('2nd', 'Monday', 11),
            ('2nd', 'Tuesday', 12),
            ('2nd', 'Wednesday', 13),
            ('2nd', 'Thursday', 14),
            ('2nd', 'Friday', 8),
            ('2nd', 'Saturday', 9),
            ('2nd', 'Sunday', 10),

            ('3rd', None, 18),
            ('3rd', 'Monday', 18),
            ('3rd', 'Tuesday', 19),
            ('3rd', 'Wednesday', 20),
            ('3rd', 'Thursday', 21),
            ('3rd', 'Friday', 15),
            ('3rd', 'Saturday', 16),
            ('3rd', 'Sunday', 17),

            ('4th', None, 25),
            ('4th', 'Monday', 25),
            ('4th', 'Tuesday', 26),
            ('4th', 'Wednesday', 27),
            ('4th', 'Thursday', 28),
            ('4th', 'Friday', 22),
            ('4th', 'Saturday', 23),
            ('4th', 'Sunday', 24),

            ('4th', None, 25),
            ('4th', 'Monday', 25),
            ('4th', 'Tuesday', 26),
            ('4th', 'Wednesday', 27),
            ('4th', 'Thursday', 28),
            ('last', 'Friday', 29),
            ('last', 'Saturday', 30),
            ('last', 'Sunday', 24), ]
        for wom, dow, expected in match_list:
            with self.subTest(wom=wom, dow=dow, expected=expected):
                self.evt.wom = wom
                self.evt.dow = dow
                self.evt.fixed_day()
                self.assertEqual(expected, self.evt.date.day)

    def test_branch_failure_logs_invalid_logic_branch(self):
        mock_logging = self.mock_patch('event.logging')
        self.evt.fixed_day()
        msg = "Unhandled date condition."
        mock_logging.error.assert_called_with(msg=msg)

    def test_moy_present_does_not_cause_logic_branch_failure(self):
        self.evt.moy = 'May'
        mock_logging = self.mock_patch('event.logging')
        self.evt.fixed_day()
        mock_logging.error.assert_not_called()
