from mock import ANY, patch, Mock, MagicMock, PropertyMock, mock_open
import os
import textwrap
import unittest

from data.automated_triage.event_classifier import lib
from data.automated_triage.triagers import lib as triager_lib
from sim.marvel.api.constants import M2_GENERATE_PERSISTENT_LOG_TEST_TEMPLATE

PACKAGE_ROOT = 'data.automated_triage.event_classifier.lib.'

class RosParamsProcessorTest(unittest.TestCase):

    def setUp(self):
        self._meta_id = '20180616T044017-kitt_10'

    def test_artifact_download_subdir(self):
        processor = lib.RosParamsProcessor(self._meta_id)
        default_subdir = os.path.join(processor.artifact_download_dir, self._meta_id)
        self.assertEqual(default_subdir, processor.artifact_download_subdir())
        custom_subdir = os.path.join(processor.artifact_download_dir, self._meta_id, 'haha')
        self.assertEqual(custom_subdir, processor.artifact_download_subdir('haha'))

    @patch(PACKAGE_ROOT + 'os.listdir', autospec=True)
    def test_get_issue_files(self, mock_listdir):
        mock_listdir.return_value = ['axe_2.txt',
            'cat.md',
            'axe_3.txt',
            'wire.txt',
            'dog.md']
        processor = lib.RosParamsProcessor(self._meta_id)
        files = processor.get_issue_files('/home/issues', '', '.txt')
        self.assertEqual(len(files), 3)
        files = processor.get_issue_files('/home/issues', 'axe', '.txt')
        self.assertEqual(len(files), 2)
        files = processor.get_issue_files('/home/issues', '', '.md')
        self.assertEqual(len(files), 2)
        files = processor.get_issue_files('/home/issues', 'dog', '.md')
        self.assertEqual(len(files), 1)

    def test_download_params_tar(self):
        processor = lib.RosParamsProcessor(self._meta_id)
        # Too many RosParamsTars
        processor._run_files_memo = {'success': True,
            'files': [{'type': 'ROSParamsTar'}, {'type': 'ROSParamsTar'}]}
        with self.assertRaises(lib.RunFileIntegrityError) as e:
            processor.download_params_tar()

        # No RosParamsTar
        processor._run_files_memo = {'success': True, 'files': []}
        with self.assertRaises(lib.RunFileIntegrityError) as e:
            processor.download_params_tar()

        # Exactly 1 Params Tar
        file_name = '20180618T175423-kitt_01-1ed55c11-0000.tar.gz'
        processor._run_files_memo = {'success': True,
            'files': [{'type': 'ROSParamsTar', 'name': file_name}]}
        params_tar = processor.download_params_tar()
        self.assertEqual(os.path.join(processor.artifact_download_subdir(), file_name), params_tar)

    def test_get_file_timestamps(self):
        filepaths = ['/home/note_1_2_1231.txt', '/home/disengagment_2_1541.txt', '/home/note_1000.txt']
        processor = lib.RosParamsProcessor(self._meta_id)
        timestamps = processor.get_file_timestamps(filepaths)
        self.assertEqual(timestamps, [1231.0, 1541.0, 1000.0])


class FreeFunctionTests(unittest.TestCase):


    def setUp(self):
        self.timestamp_ns = 1528406421389000960
        self.note = Mock(message='Keurig Coffee', event=Mock(timestamp=self.timestamp_ns, type='NOTE'))
        self.disengagement = Mock(message='Coffee Beans', reason='CRITICAL')
        self.disengagement.event = Mock(timestamp=self.timestamp_ns, type='DISENGAGEMENT')
        self.coredump = Mock(executable='planner', event=Mock(type='COREDUMP', timestamp=self.timestamp_ns))
        self.faultevent = Mock(event=Mock(type='FAULTEVENT', timestamp=self.timestamp_ns))
        self.nogo = Mock(event=Mock(type='NOGO', timestamp=self.timestamp_ns), source='AI_MONITOR', end_source='TELEOP')
        self.offline_note = Mock(event=Mock(type='OFFLINE_NOTE', timestamp=self.timestamp_ns), message='Offline msg')

    def test_get_event_metadata(self):
        client = MagicMock()
        event = MagicMock()
        type(client).event = PropertyMock(return_value=event)
        getEvents = MagicMock()
        event.getEvents.return_value = getEvents
        result = MagicMock()
        getEvents.result.return_value = result
        type(result).items = PropertyMock(return_value = [Mock(id="1", type='NOTE'), Mock(id="2", type='DISENGAGEMENT'), Mock(id="3", type='DISENGAGEMENT'),
            Mock(id="4", type='COREDUMP'), Mock(id="4", type='FAULTEVENT')])
        expected_dict = {'NOTE': 1, 'DISENGAGEMENT': 2, 'FAULTEVENT': 1, 'COREDUMP': 1}
        self.assertEqual(expected_dict, lib.get_event_metadata(client, 'meta_id', None))

    def test_get_event_metadata_fail(self):
        client = MagicMock()
        event = MagicMock()
        type(client).event = PropertyMock(return_value=event)
        getEvents = MagicMock()
        event.getEvents.return_value = getEvents
        getEvents.result.side_effect = lib.HTTPNotFound(Mock(status_code=404))
        self.assertEqual(lib.get_event_metadata(client, 'meta_id', None), {})

    def test_get_run_events_empty(self):
        event_metadata = {}
        events = lib.get_run_events(Mock(), event_metadata, 'some_id', None)
        self.assertEqual(events,
            {'DISENGAGEMENT': [], 'NOTE': [], 'COREDUMP': [], 'NOGO': [], 'TELEOPS_ENGAGEMENT': [], 'OFFLINE_NOTE': []})

    def test_get_run_events(self):
        client = MagicMock()
        event_metadata =  {'DISENGAGEMENT': 3, 'NOTE': 3, 'COREDUMP': 3, 'NOGO': 3}
        # Mock out the disengagement results
        disengagement = MagicMock()
        type(client).disengagement = PropertyMock(return_value=disengagement)
        getDisengagements = MagicMock()
        disengagement.getDisengagements.return_value = getDisengagements
        result_1 = MagicMock()
        getDisengagements.result.return_value = result_1
        type(result_1).items = PropertyMock(return_value=[1,2,3])

        # Mock out the Note Results
        note = MagicMock()
        type(client).note = PropertyMock(return_value=note)
        getNotes = MagicMock()
        note.getNotes.return_value = getNotes
        result_2 = MagicMock()
        getNotes.result.return_value = result_2
        type(result_2).items = PropertyMock(return_value=[4,5,6])

        # Mock out the Coredump Results
        coredump = MagicMock()
        type(client).coredump = PropertyMock(return_value=coredump)
        getCoredumps = MagicMock()
        coredump.getCoredumps.return_value = getCoredumps
        result_3 = MagicMock()
        getCoredumps.result.return_value = result_3
        type(result_3).items = PropertyMock(return_value=[7,8,9])

        # Mock out the Nogo Results
        nogo = MagicMock()
        type(client).nogo = PropertyMock(return_value=nogo)
        getNogos = MagicMock()
        nogo.getNogos.return_value = getNogos
        result_4 = MagicMock()
        getNogos.result.return_value = result_4
        type(result_4).items = PropertyMock(return_value=[10,11,12])


        events = lib.get_run_events(client, event_metadata, 'some_meta_id', None)

        self.assertEqual(events['NOTE'], [4,5,6])
        self.assertEqual(events['DISENGAGEMENT'], [1,2,3])
        self.assertEqual(events['COREDUMP'], [7,8,9])
        self.assertEqual(events['NOGO'], [10,11,12])

    def test_get_run_events(self):
        client = MagicMock()
        event_metadata =  {'DISENGAGEMENT': 1}
        # Mock out the disengagement results
        disengagement = MagicMock()
        type(client).disengagement = PropertyMock(return_value=disengagement)
        getDisengagements = MagicMock()
        disengagement.getDisengagements.return_value = getDisengagements
        result_1 = MagicMock()
        getDisengagements.result.return_value = result_1
        disengagements = [MagicMock(event=Mock(id="1")), MagicMock(event=Mock(id="2")), MagicMock(event=Mock(id="3"))]
        type(result_1).items = PropertyMock(return_value=disengagements)

        events = lib.get_run_events(client, event_metadata, 'some_meta_id', "2")

        self.assertEqual(events['NOTE'], [])
        self.assertEqual(events['COREDUMP'], [])
        self.assertEqual(events['NOGO'], [])
        self.assertEqual(events['DISENGAGEMENT'], [disengagements[1]])

    @patch(PACKAGE_ROOT + 'os.path.exists', autospec=True)
    def test_get_event_timestamps_no_issue_dir(self, mock_exists):
        '''
        Issue files were not saved, so get timestamps for all notes/disengagements
        '''
        mock_processor = Mock()
        mock_processor.download_params_tar.return_value = 'foo'
        mock_processor.extract_params_tar.return_value = 'bar'
        mock_exists.return_value = False
        run_events = {
            'NOTE': [Mock(event=Mock(timestamp=1E9 * 2)), Mock(event=Mock(timestamp=1E9 * 4)), Mock(event=Mock(timestamp=1E9 * 6))],
            'DISENGAGEMENT': [Mock(event=Mock(timestamp=1E9 * 5)), Mock(event=Mock(timestamp=1E9 * 7)), Mock(event=Mock(timestamp=1E9 * 9))],
        }
        note_ts, disengagement_ts = lib.get_event_timestamps(mock_processor, run_events)
        self.assertEqual([2.0, 4.0, 6.0], note_ts)
        self.assertEqual([5.0, 7.0, 9.0], disengagement_ts)
        self.assertEqual(mock_processor.get_issue_files.call_count, 0)
        self.assertEqual(mock_processor.get_file_timestamps.call_count, 0)


    @patch(PACKAGE_ROOT + 'os.path.exists', autospec=True)
    def test_get_event_timestamps_issue_dir(self, mock_exists):
        '''
        Issue files were not saved, so get timestamps for all notes/disengagements
        '''
        mock_processor = Mock()
        mock_processor.download_params_tar.return_value = 'foo'
        mock_processor.extract_params_tar.return_value = 'bar'
        mock_processor.get_issue_files.side_effect = [
         ['home/note_2.txt', 'home/note_4.txt', 'home/note_6.txt'],
         ['home/disengagement_5.txt', 'home/disengagement_7.txt', 'home/disengagement_9.txt'],
        ]
        mock_processor.get_file_timestamps.side_effect = [
         [2.0, 4.0, 6.0],
         [5.0, 7.0, 9.0],
        ]
        mock_exists.return_value = True
        note_ts, disengagement_ts = lib.get_event_timestamps(mock_processor, {})
        self.assertEqual([2.0, 4.0, 6.0], note_ts)
        self.assertEqual([5.0, 7.0, 9.0], disengagement_ts)
        mock_processor.download_params_tar.assert_called_once()
        mock_processor.extract_params_tar.assert_called_once()
        self.assertEqual(mock_processor.get_issue_files.call_count, 2)
        self.assertEqual(mock_processor.get_file_timestamps.call_count, 2)

    @patch(PACKAGE_ROOT + 'os.path.exists', autospec=True)
    def test_get_event_timestamps_offline_note(self, mock_exists):
        '''
        Issue files were not saved, so get timestamps for all notes/disengagements
        '''
        mock_processor = Mock()
        mock_processor.download_params_tar.return_value = 'foo'
        mock_processor.extract_params_tar.return_value = 'bar'
        mock_exists.return_value = False
        run_events = {
            'NOTE': [Mock(event=Mock(timestamp=1E9 * 2)), Mock(event=Mock(timestamp=1E9 * 4)), Mock(event=Mock(timestamp=1E9 * 6))],
            'DISENGAGEMENT': [Mock(event=Mock(timestamp=1E9 * 5)), Mock(event=Mock(timestamp=1E9 * 7)), Mock(event=Mock(timestamp=1E9 * 9))],
            'OFFLINE_NOTE': [Mock(event=Mock(timestamp=1E9 * 10)), Mock(event=Mock(timestamp=1E9 * 12))]
        }
        note_ts, disengagement_ts = lib.get_event_timestamps(mock_processor, run_events)
        self.assertEqual([2.0, 4.0, 6.0, 10.0, 12.0], note_ts)
        self.assertEqual([5.0, 7.0, 9.0], disengagement_ts)

    def test_get_event_issue(self):
        jira_client = MagicMock()
        jira_client.search_issues.return_value = [{'id': '123124'}]
        issues = lib.get_event_issue(jira_client, 'event_id')
        self.assertEqual(issues, [{'id': '123124'}])
        jira_client.search_issues.assert_called_with('project = TRG and \"Vehicle Event ID\" ~ event_id')

    def test_get_report_from_event_triager(self):
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        getReports = MagicMock()
        reports.getReports.return_value = getReports
        result_1 = MagicMock()
        getReports.result.return_value = result_1
        type(result_1).items = PropertyMock(return_value=['report_1', 'report_2'])
        report = lib.get_report_from_event_triager(client, 'event_id', 'triager')
        self.assertEqual(report, 'report_1')

    def test_get_report_from_event_triager_failed(self):
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        getReports = MagicMock()
        reports.getReports.return_value = getReports
        getReports.result.side_effect = lib.HTTPNotFound(Mock(status_code=404))
        report = lib.get_report_from_event_triager(client, 'event_id', 'triager')
        self.assertEqual(report, None)

    def test_extract_custom_labels_basic(self):
        self.assertEqual(set(), lib.extract_custom_labels(''))
        self.assertEqual(set(), lib.extract_custom_labels('Hello Goodbye'))
        self.assertEqual({'#hello'}, lib.extract_custom_labels('#Hello Goodbye'))
        self.assertEqual({'#hello'}, lib.extract_custom_labels('#Hello #hello Goodbye'))

    @patch(PACKAGE_ROOT + 'get_fuzzed_labels', autospec=True)
    def test_extract_custom_labels_with_corrections(self, mock_get_fuzzed):
        message = '#how #do #yu #eet'
        mock_get_fuzzed.return_value = {('#yu', '#you'), ('#eet', '#eat')}
        self.assertEqual({'#how', '#do', '#you', '#eat'}, lib.extract_custom_labels(message))

    def test_get_event_geofences_none(self):
        mock_event = Mock(event=Mock(latLonCoordinate=None))
        self.assertEqual(lib.get_event_geofences(mock_event), [])

    def test_get_event_geofences(self):
        # This should be in SF_1
        mock_event = Mock(event=Mock(latLonCoordinate=(37.800084, -122.400676)))
        self.assertEqual(lib.get_event_geofences(mock_event), set(['SF_1']))

    def test_generate_triager_labels(self):
        mock_event = Mock(event=Mock(type='COREDUMP'))
        mock_triagers_1 = {'Vanilla': Mock(), 'BrakeTap': Mock()}
        mock_triagers_1['Vanilla'].generate_ticket_labels.return_value = []
        mock_triagers_1['BrakeTap'].generate_ticket_labels.return_value = ['x', 'yz', 'p' * 256]
        # No Triagers fired off
        mock_reports_0 = [Mock(triager='Vanilla', result=False), Mock(triager='BrakeTap', result=False)]
        self.assertEqual([], lib.generate_triager_labels(mock_event, mock_reports_0, mock_triagers_1))

        # Only Vanilla Triager fired off
        mock_reports_1 = [Mock(triager='Vanilla', result=True), Mock(triager='BrakeTap', result=False)]
        for report in mock_reports_1:
            report.triager_input = '{}'
            report.triager_output = '{}'

        self.assertEqual(['VanillaUponCreation', 'not_deduped', 'Triager:Vanilla'],
            lib.generate_triager_labels(mock_event, mock_reports_1, mock_triagers_1))

        # Both Vanilla and BrakeTap Fired Off with a label that was too big
        mock_reports_2 = [Mock(triager='Vanilla', result=True), Mock(triager='BrakeTap', result=True)]
        for report in mock_reports_2:
            report.triager_input = '{}'
            report.triager_output = '{}'
        self.assertEqual(['Triager:Vanilla', 'Triager:BrakeTap', 'x', 'yz'],
            lib.generate_triager_labels(mock_event, mock_reports_2, mock_triagers_1))


    def test_run_classifiers(self):
        mock_event = Mock(event=Mock(id='1234'))
        mock_ticket_client = Mock()
        # Passing the Mock Constructor function as return value
        mock_ticket_client.get_model.return_value = Mock
        mock_vanilla_triager = Mock()
        mock_vanilla_triager.classify.return_value = Mock(result=False, input={'a': True})

        mock_brake_tap_triager = Mock()
        mock_brake_tap_triager.classify.return_value = Mock(result=True, input={'b': False})

        mock_bad_depth_triager = Mock()
        mock_bad_depth_triager.classify.return_value = Mock(result=True, input={'c': 1})

        triagers = {'bad_depth': mock_bad_depth_triager,
                    'brake_tap': mock_brake_tap_triager,
                    'Vanilla': mock_vanilla_triager}

        reports = lib.run_classifiers(mock_ticket_client, mock_event, triagers)
        self.assertEqual(reports['Vanilla'].result, False)
        self.assertEqual(reports['bad_depth'].result, True)
        self.assertEqual(reports['brake_tap'].result, True)

        self.assertEqual(reports['Vanilla'].event_id, '1234')
        self.assertEqual(reports['bad_depth'].event_id, '1234')
        self.assertEqual(reports['brake_tap'].event_id, '1234')

        self.assertEqual(reports['Vanilla'].triager, 'Vanilla')
        self.assertEqual(reports['bad_depth'].triager, 'bad_depth')
        self.assertEqual(reports['brake_tap'].triager, 'brake_tap')

        self.assertEqual(reports['Vanilla'].triager_input, '{"a": true}')
        self.assertEqual(reports['brake_tap'].triager_input, '{"b": false}')
        self.assertEqual(reports['bad_depth'].triager_input, '{"c": 1}')

    def test_process_classifications(self):
        mock_event = Mock(event=Mock(id='1234'))
        reports = {
            'brake_tap': Mock(result=False, triager_output=None, assignee=None),
            'bad_depth': Mock(result=True, triager_output=None, assignee=None),
            'Vanilla': Mock(result=True, triager_output=None, assignee=None)
        }

        mock_vanilla_triager = Mock()
        mock_vanilla_triager.process.return_value = Mock(assignee='Unknown', output={'1': 2})
        mock_bad_depth_triager = Mock()
        mock_bad_depth_triager.process.return_value = Mock(assignee='Vision', output={'3': 2})
        mock_brake_tap_triager = Mock()

        triagers = {
            'brake_tap': mock_brake_tap_triager,
            'bad_depth': mock_bad_depth_triager,
            'Vanilla': mock_vanilla_triager
        }

        classifications = lib.process_reports(mock_event, reports, triagers)

        # Only Vanilla and bad_depth should have fields filled in.
        self.assertEqual(classifications['Vanilla'].result, True)
        self.assertEqual(classifications['bad_depth'].result, True)
        self.assertEqual(classifications['brake_tap'].result, False)

        self.assertEqual(classifications['Vanilla'].assignee, 'Unknown')
        self.assertEqual(classifications['bad_depth'].assignee, 'Vision')
        self.assertEqual(classifications['brake_tap'].assignee, None)

        self.assertEqual(classifications['Vanilla'].triager_output, '{"1": 2}')
        self.assertEqual(classifications['bad_depth'].triager_output, '{"3": 2}')
        self.assertEqual(classifications['brake_tap'].triager_output, None)

    def test_upload_reports_empty(self):
        self.assertEqual(lib.upload_reports(Mock(), []), [])

    def test_upload_reports(self):
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        addReport = MagicMock()
        reports.addReport.return_value = addReport
        addReport.result.side_effect = [Mock(), Mock(), Mock()]

        mock_reports = [Mock(id=1), Mock(id=2), Mock(id=3)]
        desired_reports = lib.upload_reports(client, mock_reports)
        report_ids = [report.id for report in desired_reports]
        self.assertEqual(report_ids, [1, 2, 3])

    @patch(PACKAGE_ROOT + 'get_report_from_event_triager', autospec=True)
    def test_upload_reports_existing_reports(self, mock_get_report):
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        addReport = MagicMock()
        reports.addReport.return_value = addReport
        addReport.result.side_effect = [lib.HTTPConflict(Mock(status_code=409)),
            lib.HTTPConflict(Mock(status_code=409)),
            lib.HTTPConflict(Mock(status_code=409))]

        mock_reports = [Mock(id=1), Mock(id=2), Mock(id=3)]
        mock_get_report.side_effect = [Mock(id=4), Mock(id=5), Mock(id=6)]

        desired_reports = lib.upload_reports(client, mock_reports)
        report_ids = [report.id for report in desired_reports]
        self.assertEqual(report_ids, [4, 5, 6])

    @patch(PACKAGE_ROOT + '_override_existing_report', autospec=True)
    @patch(PACKAGE_ROOT + 'get_report_from_event_triager', autospec=True)
    def test_upload_reports_existing_reports_force(self, mock_get_report, mock_overrides):
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        addReport = MagicMock()
        reports.addReport.return_value = addReport
        addReport.result.side_effect = [lib.HTTPConflict(Mock(status_code=409)),
            lib.HTTPConflict(Mock(status_code=409)),
            lib.HTTPConflict(Mock(status_code=409))]

        mock_reports = [Mock(id=1), Mock(id=2), Mock(id=3)]
        mock_get_report.side_effect = [Mock(id=4), Mock(id=5), Mock(id=6)]
        mock_overrides.side_effect = [Mock(), Mock(), Mock()]

        desired_reports = lib.upload_reports(client, mock_reports, True)
        report_ids = [report.id for report in desired_reports]
        self.assertEqual(report_ids, [1, 2, 3])

    @patch(PACKAGE_ROOT + 'get_report_from_event_triager', autospec=True)
    def test_upload_reports_existing_reports_force_failure(self, mock_get_report):
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        addReport = MagicMock()
        reports.addReport.return_value = addReport
        addReport.result.side_effect = [lib.HTTPConflict(Mock(status_code=409)),
            lib.HTTPConflict(Mock(status_code=409)),
            lib.HTTPConflict(Mock(status_code=409))]

        mock_reports = [Mock(id=1), Mock(id=2), Mock(id=3)]
        mock_get_report.side_effect = [None, None, None]

        with self.assertRaises(lib.VehicleTicketsError):
            lib.upload_reports(client, mock_reports, True)

    def test_generate_nogo_labels(self):
        nogo_1 = Mock(source='AI_MONITOR', end_mode='RELEASE')
        nogo_2 = Mock(source='COREDASH', end_mode='RELEASE')
        self.assertEqual(lib.generate_nogo_labels(nogo_1), [])
        self.assertEqual(lib.generate_nogo_labels(nogo_2), ['#to_success'])

    def test_generate_teleops_engagement_labels(self):
        teleops_engagement_1 = Mock(flag_for_review=True)
        teleops_engagement_2 = Mock(flag_for_review=False)
        self.assertEqual(lib.generate_teleops_engagement_labels(teleops_engagement_1), ['to-report', 'to-report-review'])
        self.assertEqual(lib.generate_teleops_engagement_labels(teleops_engagement_2), ['to-report'])

    def test_get_argus_link(self):
        mock_event = Mock(event=Mock(timestamp=20 * 1E9, run_id='129048120-kitt_01'))
        self.assertEqual(lib.get_argus_link(mock_event), 'https://argus.zooxlabs.com/129048120-kitt_01?time=10.0')

    def test_extract_triage_bucket_wrong_type(self):
        mock_coredump = Mock(event=Mock(type='COREDUMP'))
        self.assertEqual(lib.extract_triage_bucket(mock_coredump), None)

    def test_extract_triage_bucket_too_many_tags(self):
        mock_disengagement = Mock(event=Mock(type='DISENGAGEMENT'), message='^lc and ^fp')
        self.assertEqual(lib.extract_triage_bucket(mock_disengagement), None)

    def test_extract_triage_bucket_no_valid_tag(self):
        mock_note = Mock(event=Mock(type='NOTE'), message='^pokemon')
        self.assertEqual(lib.extract_triage_bucket(mock_note), None)

    def test_extract_triage_bucket(self):
        mock_note = Mock(event=Mock(type='NOTE'), message='^pcp')
        self.assertEqual(lib.extract_triage_bucket(mock_note), 'Perception Failure')

    def test_is_point_to_point_run(self):
        run_1 = {'short_name': 'p2pdc'}
        run_2 = {'short_name': 'p2PdC'}
        run_3 = {'short_name': 'Nothing'}
        self.assertEqual(lib.is_point_to_point_run(run_1), True)
        self.assertEqual(lib.is_point_to_point_run(run_2), True)
        self.assertEqual(lib.is_point_to_point_run(run_3), False)

    def test_generate_ticket_title(self):
        self.assertEqual('2018-06-07: Note: Keurig Coffee', lib.generate_standard_ticket_title(self.note))
        self.assertEqual('2018-06-07: Disengagement: Coffee Beans',
            lib.generate_standard_ticket_title(self.disengagement))
        self.assertEqual('planner crashed on 2018-06-07 at 14:20:21',
            lib.generate_standard_ticket_title(self.coredump))
        self.assertEqual('', lib.generate_standard_ticket_title(self.faultevent))
        self.assertEqual('2018-06-07 at 14:20:21: Nogo: Started by AI_MONITOR, Ended by TELEOP',
            lib.generate_standard_ticket_title(self.nogo))
        self.assertEqual('2018-06-07: Offline Note: Offline msg',
            lib.generate_standard_ticket_title(self.offline_note))

    @patch('infra.data_catalog.client.data_rest_api.get_run_metadata', autospec=True)
    def test_generate_run_labels(self, get_metadata_mock):
        get_metadata_mock.return_value={'success': True, 'metadata':{}}
        run_1 = {'short_name': 'p2pdc'}
        run_2 = {'short_name': 'Nothing'}
        self.assertEqual(lib.generate_run_labels(run_1), ['P2PDC'])
        self.assertEqual(lib.generate_run_labels(run_2), [])

        get_metadata_mock.return_value={'success': True,
                                        'metadata':{'mission_config_mode': 'MODE1'}}

        self.assertEqual(lib.generate_run_labels(run_2), ['mission_config_mode:MODE1'])

    def test_generate_event_labels(self):
        event_1 = Mock(event=Mock(type='FAULTEVENT', latLonCoordinate=None, autonomous_mode='MANUAL', vehicle='kitt_01'))
        self.assertEqual(set(lib.generate_event_labels(event_1)), set(['Event:FAULTEVENT', 'MANUAL', 'kitt-01']))
        event_2 = Mock(event=Mock(type='DISENGAGEMENT', autonomous_mode='AUTONOMOUS', vehicle='kitt_01', latLonCoordinate=[37.418711, -122.205340], timestamp=1506087912*1E9), message='#hello and goodbye', reason='REQUESTED', is_zfr=True)
        self.assertEqual(set(lib.generate_event_labels(event_2)), set(['Event:DISENGAGEMENT', '#hello', 'SLAC', 'SUNSTATE_TWILIGHT', 'AUTONOMOUS', 'kitt-01', 'REQUESTED', 'zfr-report']))
        event_3 = Mock(event=Mock(type='NOGO', autonomous_mode='LONG_CONTROL_ONLY', vehicle='kitt_01', latLonCoordinate=[37.418711, -122.205340], timestamp=1506087912*1E9), source='COREDASH', end_mode='RELEASE')
        self.assertEqual(set(lib.generate_event_labels(event_3)), set(['Event:NOGO', '#to_success', 'SLAC', 'SUNSTATE_TWILIGHT', 'LONG_CONTROL_ONLY', 'kitt-01']))

    @patch('infra.data_catalog.client.data_rest_api.get_run_metadata', autospec=True)
    def test_generate_ticket_labels(self, get_metadata_mock):
        get_metadata_mock.return_value={'success': True,
                                        'metadata':{'mission_config_mode': 'MODE2'}}
        run = {'short_name': 'p2pdc'}
        event = Mock(
            event=Mock(
                type='DISENGAGEMENT',
                latLonCoordinate=[37.418711, -122.205340],
                autonomous_mode='LONG_CONTROL_ONLY',
                vehicle='kitt_04',
                timestamp=1506087912*1E9),
            message='#hello and goodbye',
            reason='REQUESTED',
        )
        triagers = {'Vanilla': Mock(), 'BrakeTap': Mock()}
        triagers['Vanilla'].generate_ticket_labels.return_value = []
        triagers['BrakeTap'].generate_ticket_labels.return_value = ['x', 'yz', 'p' * 256]
        reports = [Mock(triager='Vanilla', result=True), Mock(triager='BrakeTap', result=True)]
        for report in reports:
            report.triager_input = '{}'
            report.triager_output = '{}'

        self.assertEqual(set(lib.generate_ticket_labels(event, run, reports, triagers)),
            set(['from_vehicle',
                 'P2PDC',
                 'kitt-04',
                 'LONG_CONTROL_ONLY',
                 'Event:DISENGAGEMENT',
                 '#hello',
                 'SLAC',
                 'REQUESTED',
                 'SUNSTATE_TWILIGHT',
                 'Triager:Vanilla',
                 'Triager:BrakeTap',
                 'x',
                 'mission_config_mode:MODE2',
                 'yz']))

    def test_generate_persistent_scenario_link(self):
        run_meta = '20200305T205110-kitt_21'
        timestamp_ns = 1506087912*1E9
        jira_key = 'TRG-12345'
        event = Mock(
            event=Mock(
                type='DISENGAGEMENT',
                latLonCoordinate=[37.418711, -122.205340],
                autonomous_mode='LONG_CONTROL_ONLY',
                vehicle='kitt_04',
                run_id=run_meta,
                timestamp=timestamp_ns),
            message='#hello and goodbye',
            reason='REQUESTED',
        )
        generate_section = triager_lib.generate_persistent_scenario_link(event, '#d3f8d3', jira_key)
        target_uri = M2_GENERATE_PERSISTENT_LOG_TEST_TEMPLATE.format(meta=run_meta, timestamp=timestamp_ns / 1e9, jira_id = jira_key)
        reference_section = textwrap.dedent("""\
            {panel:bgColor=#d3f8d3}
            {color:#303030}Persistent log test{color}
            {panel}
            {panel}
            [Generate persistent log test|""" + target_uri + """] (this may take some time; please do not reschedule the job).
            [Check if persistent scenario exists in github|https://git.zooxlabs.com/zooxco/driving/blob/master/log_tests/scenarios/persistent_log_tests/TRG-12345.pbtxt]
            {panel}""")
        self.assertEqual(generate_section, reference_section)

    def test_override_existing_report(self):
        reportId = 'some_id'
        mock_report = Mock(result=True, assignee='HA', triager_input='{}', triager_output='{}')
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        modifyReport = MagicMock()
        reports.modifyReport.return_value = modifyReport
        modifyReport.result.return_value = MagicMock()
        # Call should succeed with no exception
        lib._override_existing_report(client, reportId, mock_report)

    def test_override_existing_report_failed(self):
        reportId = 'some_id'
        mock_report = Mock(result=True, assignee='HA', triager_input='{}', triager_output='{}')
        client = MagicMock()
        reports = MagicMock()
        type(client).reports = PropertyMock(return_value=reports)
        modifyReport = MagicMock()
        reports.modifyReport.return_value = modifyReport
        modifyReport.result.side_effect = RuntimeError
        # Call should succeed with no exception
        with self.assertRaises(lib.VehicleTicketsError):
            lib._override_existing_report(client, reportId, mock_report)

    def test_get_event_priority(self):
        mock_note = Mock(event=Mock(type='NOTE'))
        self.assertEqual(lib.get_event_priority(mock_note), None)

        mock_caution_disengagement = Mock(event=Mock(type='DISENGAGEMENT'), reason='CAUTION')
        self.assertEqual(lib.get_event_priority(mock_caution_disengagement), 'High')

        mock_critical_disengagement = Mock(event=Mock(type='DISENGAGEMENT'), reason='CRITICAL')
        self.assertEqual(lib.get_event_priority(mock_critical_disengagement), 'Highest')

    @patch('infra.data_catalog.client.data_rest_api.get_run_metadata', autospec=True)
    def test_get_release_version_no_dc(self, mock_run_metadata):
        mock_run_metadata.return_value = {'success': False}
        self.assertEqual(lib.get_release_version('meta_id'), None)

    @patch('infra.data_catalog.client.data_rest_api.get_run_metadata', autospec=True)
    def test_get_release_version_missing_keys(self, mock_run_metadata):
        mock_run_metadata.return_value = {'success': True, 'missing_keys': ['release_version']}
        self.assertEqual(lib.get_release_version('meta_id'), None)

    @patch('infra.data_catalog.client.data_rest_api.get_run_metadata', autospec=True)
    def test_get_release_version(self, mock_run_metadata):
        mock_run_metadata.return_value = {'success': True,
            'metadata': {'release_version': 'HAHAHA\n'},
            'missing_keys': []
        }
        self.assertEqual(lib.get_release_version('meta_id'), 'HAHAHA')

    def test_generate_disengagement_labels(self):
        mock_critical = Mock(reason='CRITICAL', message='#winning #Nogo')
        self.assertEqual(lib.generate_disengagement_labels(mock_critical),
            ['UNREVIEWED', 'SAFETY_ACTION', 'CRITICAL'])

        mock_requested = Mock(reason='REQUESTED', message='#winning #Nogo')
        self.assertEqual(lib.generate_disengagement_labels(mock_requested),
            ['REQUESTED'])

        mock_caution = Mock(reason='CAUTION', message='#winning #Nogo')
        self.assertEqual(lib.generate_disengagement_labels(mock_caution),
            ['CAUTION'])

        mock_caution_downgrade = Mock(reason='CAUTION', message='#Good #Nogo')
        self.assertEqual(lib.generate_disengagement_labels(mock_caution_downgrade),
            ['REQUESTED'])

        mock_oodd_downgrade = Mock(reason='OUTSIDE_ODD', message='#training #Nogo')
        self.assertEqual(lib.generate_disengagement_labels(mock_caution_downgrade),
            ['REQUESTED'])

    def test_get_fuzzed_labels_basic(self):
        self.assertEqual({('ambulance', '#ambulance')},
            lib.get_fuzzed_labels(set(['ambulance']), lib.HASHTAGS))

        self.assertEqual({('ehaust', '#exhaust'), ('scooter', '#escooter'), ('amulance', '#ambulance')},
            lib.get_fuzzed_labels(set(['ehaust', 'amulance', 'scooter']), lib.HASHTAGS))

    def test_generate_mentioned_users(self):
        mock_event = Mock(event=Mock(type='NOTE',
                                     timestamp=8888,
                                     latLonCoordinate=None),
                          message="hey @@username1 and @@username2")
        description = lib._generate_mentioned_users(mock_event, lib.GRAY_COLOR)
        self.assertTrue(
            description.find('*Users*: [~username1], [~username2]') > 0)

    def test_generate_mentioned_users_no_users(self):
        mock_event = Mock(event=Mock(type='NOTE',
                                     timestamp=8888,
                                     latLonCoordinate=None),
                          message="a message without users mentioned")
        description = lib._generate_mentioned_users(mock_event, lib.GRAY_COLOR)
        self.assertEqual(description, '')

    @patch.object(lib, 'requests')
    @patch.object(lib, 'SLACK_OAUTH_TOKEN', 'mock_slack_oauth_token')
    @patch.object(lib, 'SLACK_KEYWORDS', new={
           '@@foo': 'foo_slack_channel',
            '#bar': 'bar_slack_channel',
    })
    def test_notify_slack(self, mock_requests):
        lib.notify_slack('test_meta_id', 'JIRA-123', 'Note: foo bar')
        mock_requests.post.assert_not_called()
        lib.notify_slack('test_meta_id', 'JIRA-123', 'Note: @@foo bar')
        self.assertEqual(mock_requests.post.call_count, 1)
        mock_requests.post.assert_called_once_with(
            'https://slack.com/api/chat.postMessage',
            json={'channel': 'foo_slack_channel', 'text': ANY},
            headers={'Authorization': 'Bearer mock_slack_oauth_token'},
        )
        mock_requests.post.reset_mock()
        lib.notify_slack('test_meta_id', 'JIRA-123', 'Note: @@Foo #BAR')
        self.assertEqual(mock_requests.post.call_count, 2)

if __name__ == "__main__":
    unittest.main()
