#!/usr/bin/env vpython3
"""Tests for git_footers."""

from io import StringIO
import json
import os
import sys
import unittest
from unittest import mock

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import gclient_utils
import git_footers


class GitFootersTest(unittest.TestCase):
    _message = """
This is my commit message. There are many like it, but this one is mine.

My commit message is my best friend. It is my life.

"""

    _position = 'refs/heads/main@{#292272}'

    _position_footer = 'Cr-Commit-Position: %s\n' % _position

    def testFootersBasic(self):
        self.assertEqual(git_footers.split_footers('Not-A: footer'),
                         (['Not-A: footer'], [], []))
        self.assertEqual(
            git_footers.split_footers('Header\n\nActual: footer'),
            (['Header', ''], ['Actual: footer'], [('Actual', 'footer')]))
        self.assertEqual(git_footers.split_footers('\nActual: footer'),
                         ([''], ['Actual: footer'], [('Actual', 'footer')]))
        self.assertEqual(
            git_footers.split_footers('H\n\nBug:\nAlso: footer'),
            (['H', ''], ['Bug:', 'Also: footer'], [('Bug', ''),
                                                   ('Also', 'footer')]))
        self.assertEqual(git_footers.split_footers('H\n\nBug:      '),
                         (['H', ''], ['Bug:'], [('Bug', '')]))
        self.assertEqual(git_footers.split_footers('H\n\nBug: 1234     '),
                         (['H', ''], ['Bug: 1234'], [('Bug', '1234')]))
        self.assertEqual(
            git_footers.split_footers('H\n\nBug: 1234\nChange-Id: Ib4321  '),
            (['H', ''], ['Bug: 1234', 'Change-Id: Ib4321'
                         ], [('Bug', '1234'), ('Change-Id', 'Ib4321')]))

        self.assertEqual(git_footers.parse_footers(self._message), {})
        self.assertEqual(
            git_footers.parse_footers(self._message + self._position_footer),
            {'Cr-Commit-Position': [self._position]})
        self.assertEqual(
            git_footers.parse_footers(self._message + self._position_footer +
                                      self._position_footer),
            {'Cr-Commit-Position': [self._position, self._position]})
        self.assertEqual(
            git_footers.parse_footers(self._message + 'Bug:\n' +
                                      self._position_footer),
            {
                'Bug': [''],
                'Cr-Commit-Position': [self._position]
            })

    def testSkippingBadFooterLines(self):
        message = ('Title.\n'
                   '\n'
                   'Last: paragraph starts\n'
                   'It-may: contain\n'
                   'bad lines, which should be skipped\n'
                   'For: example\n'
                   '(cherry picked from)\n'
                   'And-only-valid: footers taken')
        self.assertEqual(git_footers.split_footers(message), (['Title.', ''], [
            'Last: paragraph starts', 'It-may: contain',
            'bad lines, which should be skipped', 'For: example',
            '(cherry picked from)', 'And-only-valid: footers taken'
        ], [('Last', 'paragraph starts'), ('It-may', 'contain'),
            ('For', 'example'), ('And-only-valid', 'footers taken')]))
        self.assertEqual(
            git_footers.parse_footers(message), {
                'Last': ['paragraph starts'],
                'It-May': ['contain'],
                'For': ['example'],
                'And-Only-Valid': ['footers taken']
            })

    def testAvoidingURLs(self):
        message = ('Someone accidentally put a URL in the footers.\n'
                   '\n'
                   'Followed: by\n'
                   'http://domain.tld\n'
                   'Some: footers')
        self.assertEqual(
            git_footers.split_footers(message),
            (['Someone accidentally put a URL in the footers.', ''], [
                'Followed: by', 'http://domain.tld', 'Some: footers'
            ], [('Followed', 'by'), ('Some', 'footers')]))
        self.assertEqual(git_footers.parse_footers(message), {
            'Followed': ['by'],
            'Some': ['footers']
        })

    def testSplittingLastParagraph(self):
        message = ('Title.\n'
                   '\n'
                   'The final paragraph has some normal text first.\n'
                   'Followed: by\n'
                   'nonsense trailers and\n'
                   'Some: footers')
        self.assertEqual(git_footers.split_footers(message), ([
            'Title.', '', 'The final paragraph has some normal text first.', ''
        ], ['Followed: by', 'nonsense trailers and', 'Some: footers'
            ], [('Followed', 'by'), ('Some', 'footers')]))
        self.assertEqual(git_footers.parse_footers(message), {
            'Followed': ['by'],
            'Some': ['footers']
        })

    def testGetFooterChangeId(self):
        msg = '\n'.join([
            'whatever',
            '',
            'Change-Id: ignored',
            '',  # Above is ignored because of this empty line.
            'Change-Id: Ideadbeaf'
        ])
        self.assertEqual(['Ideadbeaf'], git_footers.get_footer_change_id(msg))
        self.assertEqual([],
                         git_footers.get_footer_change_id(
                             'desc\nBUG=not-a-valid-footer\nChange-Id: Ixxx'))
        self.assertEqual(['Ixxx'],
                         git_footers.get_footer_change_id(
                             'desc\nBUG=not-a-valid-footer\n\nChange-Id: Ixxx'))

    def testAddFooterChangeId(self):
        with self.assertRaises(AssertionError):
            git_footers.add_footer_change_id('Already has\n\nChange-Id: Ixxx',
                                             'Izzz')

        self.assertEqual(
            git_footers.add_footer_change_id('header-only', 'Ixxx'),
            'header-only\n\nChange-Id: Ixxx')

        self.assertEqual(
            git_footers.add_footer_change_id('header\n\nsome: footer', 'Ixxx'),
            'header\n\nsome: footer\nChange-Id: Ixxx')

        self.assertEqual(
            git_footers.add_footer_change_id('header\n\nBUG: yy', 'Ixxx'),
            'header\n\nBUG: yy\nChange-Id: Ixxx')

        self.assertEqual(
            git_footers.add_footer_change_id('header\n\nBUG: yy\nPos: 1',
                                             'Ixxx'),
            'header\n\nBUG: yy\nChange-Id: Ixxx\nPos: 1')

        self.assertEqual(
            git_footers.add_footer_change_id('header\n\nBUG: yy\n\nPos: 1',
                                             'Ixxx'),
            'header\n\nBUG: yy\n\nPos: 1\nChange-Id: Ixxx')

        # Special case: first line is never a footer, even if it looks line one.
        self.assertEqual(
            git_footers.add_footer_change_id('header: like footer', 'Ixxx'),
            'header: like footer\n\nChange-Id: Ixxx')

        self.assertEqual(
            git_footers.add_footer_change_id('Header.\n\nBug: v8\nN=t\nT=z',
                                             'Ix'),
            'Header.\n\nBug: v8\nChange-Id: Ix\nN=t\nT=z')

    def testAddFooter(self):
        with self.assertRaises(ValueError):
            git_footers.add_footer('', 'Invalid Footer', 'Value')

        self.assertEqual(git_footers.add_footer('', 'Key', 'Value'),
                         '\nKey: Value')

        self.assertEqual(
            git_footers.add_footer('Header with empty line.\n\n', 'Key',
                                   'Value'),
            'Header with empty line.\n\nKey: Value')

        self.assertEqual(
            git_footers.add_footer('Top\n\nSome: footer', 'Key', 'value'),
            'Top\n\nSome: footer\nKey: value')

        self.assertEqual(
            git_footers.add_footer('Top\n\nSome: footer',
                                   'Key',
                                   'value',
                                   after_keys=['Any']),
            'Top\n\nSome: footer\nKey: value')

        self.assertEqual(
            git_footers.add_footer('Top\n\nSome: footer',
                                   'Key',
                                   'value',
                                   after_keys=['Some']),
            'Top\n\nSome: footer\nKey: value')

        self.assertEqual(
            git_footers.add_footer('Top\n\nSome: footer\nOther: footer',
                                   'Key',
                                   'value',
                                   after_keys=['Some']),
            'Top\n\nSome: footer\nKey: value\nOther: footer')

        self.assertEqual(
            git_footers.add_footer('Top\n\nSome: footer\nOther: footer',
                                   'Key',
                                   'value',
                                   before_keys=['Other']),
            'Top\n\nSome: footer\nKey: value\nOther: footer')

        self.assertEqual(
            git_footers.add_footer(
                'Top\n\nSome: footer\nOther: footer\nFinal: footer',
                'Key',
                'value',
                after_keys=['Some'],
                before_keys=['Final']),
            'Top\n\nSome: footer\nKey: value\nOther: footer\nFinal: footer')

        self.assertEqual(
            git_footers.add_footer(
                'Top\n\nSome: footer\nOther: footer\nFinal: footer',
                'Key',
                'value',
                after_keys=['Other'],
                before_keys=['Some']),
            'Top\n\nSome: footer\nOther: footer\nKey: value\nFinal: footer')

    def testRemoveFooter(self):
        self.assertEqual(git_footers.remove_footer('message', 'Key'), 'message')

        self.assertEqual(
            git_footers.remove_footer('message\n\nSome: footer', 'Key'),
            'message\n\nSome: footer')

        self.assertEqual(
            git_footers.remove_footer('message\n\nSome: footer\nKey: value',
                                      'Key'), 'message\n\nSome: footer')

        self.assertEqual(
            git_footers.remove_footer(
                'message\n\nKey: value\nSome: footer\nKey: value', 'Key'),
            'message\n\nSome: footer')

    @mock.patch('sys.stdout', StringIO())
    @mock.patch(
        'sys.stdin',
        StringIO('line\r\notherline\r\n\r\n\r\nFoo: baz\r\nStill: footer'))
    def testReadStdin(self):
        self.assertEqual(git_footers.main([]), 0)
        self.assertEqual(sys.stdout.getvalue(), 'Still: footer\nFoo: baz\n')

    @mock.patch('sys.stdin',
                StringIO('line\r\nany spaces\r\n\r\n\r\nFoo: 1\nBar: 2\nFoo: 3')
                )
    def testToJson(self):
        with gclient_utils.temporary_file() as tmp:
            self.assertEqual(git_footers.main(['--json', tmp]), 0)
            with open(tmp) as f:
                js = json.load(f)
        self.assertEqual(js, {'Foo': ['3', '1'], 'Bar': ['2']})


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