# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from parameterized import parameterized  # type: ignore
import unittest
import xml.dom.minidom

import setup_modules  # pylint: disable=unused-import

import chromium_src.tools.metrics.actions.actions_model as actions_model

PRETTY_XML = """
<actions>

<action name="Action1">
  <owner>owner@chromium.org</owner>
  <description>Description1</description>
</action>

<action name="Action2" not_user_triggered="true">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <description>Description2</description>
</action>

<action name="Action3">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <owner>owner2@chromium.org</owner>
  <description>Description3</description>
</action>

<action-suffix separator="." ordering="suffix">
  <suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
  <suffix name="LongTapMenu"
      label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>

<action-suffix separator="." ordering="suffix">
  <suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
  <affected-action name="AnAction"/>
  <affected-action name="MobileMenuCloseTab"/>
</action-suffix>

</actions>
""".strip()

XML_WITH_WRONG_INDENT = """
<actions>

  <action name="Action1">
    <owner>owner@chromium.org</owner>
    <description>Description1</description>
  </action>

<action name="Action2" not_user_triggered="true">
    <obsolete>Obsolete text</obsolete>
    <owner>owner@chromium.org</owner>
    <description>Description2</description>
</action>

<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<owner>owner2@chromium.org</owner>
<description>Description3</description>
</action>

  <action-suffix separator="." ordering="suffix">
  <suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
    <suffix name="LongTapMenu"
        label="Users opened a new incognito tab by long tap menu"/>
  </action-suffix>

  <action-suffix separator="." ordering="suffix">
    <suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
    <affected-action name="AnAction"/>
    <affected-action name="MobileMenuCloseTab"/>
  </action-suffix>

</actions>
""".strip()

XML_WITH_WRONG_SINGLELINE = """
<actions>

<action name="Action1">
  <owner>
    owner@chromium.org
  </owner>
  <description>Description1</description>
</action>

<action name="Action2" not_user_triggered="true">
  <obsolete>
    Obsolete text
  </obsolete>
  <owner>owner@chromium.org</owner>
  <description>Description2</description>
</action>

<action name="Action3">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <owner>owner2@chromium.org</owner>
  <description>
    Description3
  </description>
</action>

<action-suffix separator="." ordering="suffix">
  <suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
  <suffix name="LongTapMenu"
      label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>

<action-suffix separator="." ordering="suffix">
  <suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
  <affected-action name="AnAction"/>
  <affected-action name="MobileMenuCloseTab"/>
</action-suffix>

</actions>
""".strip()

XML_WITH_WRONG_LINE_BREAK = """
<actions>
<action name="Action1">
  <owner>owner@chromium.org</owner>
  <description>Description1</description>
</action>

<action name="Action2" not_user_triggered="true">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <description>Description2</description>
</action>
<action name="Action3">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>

  <owner>owner2@chromium.org</owner>
  <description>Description3</description>
</action>

<action-suffix separator="." ordering="suffix">
  <suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
  <suffix name="LongTapMenu"
      label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>

<action-suffix separator="." ordering="suffix">
  <suffix name="LongTapMenu"
      label="Users closed a tab by long tap menu"/>
  <affected-action name="AnAction"/>
  <affected-action name="MobileMenuCloseTab"/>
</action-suffix>
</actions>
""".strip()

XML_WITH_WRONG_ORDER = """
<actions>

<action name="Action2" not_user_triggered="true">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <description>Description2</description>
</action>

<action name="Action1">
  <owner>owner@chromium.org</owner>
  <description>Description1</description>
</action>

<action name="Action3">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <owner>owner2@chromium.org</owner>
  <description>Description3</description>
</action>

<action-suffix separator="." ordering="suffix">
  <suffix name="LongTapMenu"
      label="Users opened a new incognito tab by long tap menu"/>
  <suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
</action-suffix>

<action-suffix separator="." ordering="suffix">
  <suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
  <affected-action name="MobileMenuCloseTab"/>
  <affected-action name="AnAction"/>
</action-suffix>

</actions>
""".strip()

XML_WITH_WRONG_CHILDREN_ORDER = """
<actions>

<action name="Action1">
  <description>Description1</description>
  <owner>owner@chromium.org</owner>
</action>

<action name="Action2" not_user_triggered="true">
  <owner>owner@chromium.org</owner>
  <obsolete>Obsolete text</obsolete>
  <description>Description2</description>
</action>

<action name="Action3">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <description>Description3</description>
  <owner>owner2@chromium.org</owner>
</action>

<action-suffix separator="." ordering="suffix">
  <suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
  <suffix name="LongTapMenu"
      label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>

<action-suffix separator="." ordering="suffix">
  <affected-action name="AnAction"/>
  <affected-action name="MobileMenuCloseTab"/>
  <suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
</action-suffix>

</actions>
""".strip()

XML_WITH_WRONG_ATTRIBUTE_ORDER = """
<actions>

<action name="Action1">
  <owner>owner@chromium.org</owner>
  <description>Description1</description>
</action>

<action not_user_triggered="true" name="Action2">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <description>Description2</description>
</action>

<action name="Action3">
  <obsolete>Obsolete text</obsolete>
  <owner>owner@chromium.org</owner>
  <owner>owner2@chromium.org</owner>
  <description>Description3</description>
</action>

<action-suffix ordering="suffix" separator=".">
  <suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
  <suffix name="LongTapMenu"
      label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>

<action-suffix separator="." ordering="suffix">
  <suffix label="Users closed a tab by long tap menu" name="LongTapMenu"/>
  <affected-action name="AnAction"/>
  <affected-action name="MobileMenuCloseTab"/>
</action-suffix>

</actions>
""".strip()


class ActionXmlTest(unittest.TestCase):
  def __init__(self, *args, **kwargs):
    super(ActionXmlTest, self).__init__(*args, **kwargs)
    self.maxDiff = None

  @parameterized.expand(
    [
      # Test prettify already pretty XML to verify the pretty-printed version
      # is the same.
      ('AlreadyPrettyXml', PRETTY_XML, PRETTY_XML),
      ('Indent', XML_WITH_WRONG_INDENT, PRETTY_XML),
      ('SingleLine', XML_WITH_WRONG_SINGLELINE, PRETTY_XML),
      ('LineBreak', XML_WITH_WRONG_LINE_BREAK, PRETTY_XML),
      ('Order', XML_WITH_WRONG_ORDER, PRETTY_XML),
      # The children of <action> should be sorted in the order of <obsolete>,
      # <owner> and <description>
      ('ChildrenOrder', XML_WITH_WRONG_CHILDREN_ORDER, PRETTY_XML),
    ]
  )
  def testPrettify(self, _, input_xml, expected_xml):
    result = actions_model.PrettifyTree(xml.dom.minidom.parseString(input_xml))
    self.assertMultiLineEqual(result.strip(), expected_xml)

  @parameterized.expand(
    [
      ('BadAttributeBoolean', PRETTY_XML, 'true', 'hello', 'hello'),
      ('BadSuffixNameWithSpace', PRETTY_XML, 'AppMenu', 'App Menu', 'App Menu'),
      (
        'BadAffectedActionNameWithSpace',
        PRETTY_XML,
        'AnAction',
        'An Action',
        'An Action',
      ),
      ('SuffixWithBadSeparator', PRETTY_XML, '.', '-', '-'),
      (
        'BadOrdering_IllegalWord',
        PRETTY_XML,
        'ordering="suffix"',
        'ordering="hello"',
        'hello',
      ),
    ]
  )
  def testRegex(
    self, _, pretty_input_xml, original_string, bad_string, error_string
  ):
    BAD_XML = pretty_input_xml.replace(original_string, bad_string)
    with self.assertRaises(ValueError) as context:
      actions_model.PrettifyTree(xml.dom.minidom.parseString(BAD_XML))
    self.assertIn(error_string, str(context.exception))
    self.assertIn('does not match regex', str(context.exception))


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