#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2023 Edgewall Software
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at https://trac.edgewall.org/log/.

import re
import unittest

from datetime import timedelta

from trac.admin.tests.functional import AuthorizationTestCaseSetup
from trac.test import locale_en
from trac.tests.contentgen import random_unique_camel
from trac.tests.functional import FunctionalTwillTestCaseSetup, b, \
                                  internal_error, tc
from trac.util.datefmt import datetime_now, format_date, format_datetime, \
                              localtz, utc
from trac.util.html import escape


class AdminEnumDefaultTestCaseSetup(FunctionalTwillTestCaseSetup):
    def test_default(self, enum, name):
        url = self._tester.url + '/admin/ticket/%s' % enum
        tc.go(url)
        tc.url(url + '$')
        tc.find(name)
        tc.formvalue('enumtable', 'default', name)
        tc.submit('apply')
        tc.url(url + '$')
        tc.find('radio.*checked="checked" value="%s"' % name)
        # Test the "Clear default" button
        tc.go(url)
        tc.submit('clear', formname='enumtable')
        tc.url(url + '$')
        tc.notfind(internal_error)
        tc.find('<input type="radio" name="default" value="[^>]+"/>')
        tc.notfind('type="radio" name="default" checked="checked" value=".+"')


class TestAdminComponent(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create component"""
        self._tester.create_component()


class TestAdminComponentAuthorization(AuthorizationTestCaseSetup):
    def runTest(self):
        """Check permissions required to access the Ticket Components
        panel."""
        self.test_authorization('/admin/ticket/components', 'TICKET_ADMIN',
                                "Manage Components")


class TestAdminComponentDuplicates(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create duplicate component"""
        name = "DuplicateComponent"
        self._tester.create_component(name)
        component_url = self._tester.url + "/admin/ticket/components"
        tc.go(component_url)
        tc.formvalue('addcomponent', 'name', name)
        tc.submit()
        tc.notfind(internal_error)
        tc.find('Component .* already exists')


class TestAdminComponentRemoval(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove component"""
        name = "RemovalComponent"
        self._tester.create_component(name)
        component_url = self._tester.url + "/admin/ticket/components"
        tc.go(component_url)
        tc.formvalue('component_table', 'sel', name)
        tc.submit('remove')
        tc.notfind(name)


class TestAdminComponentNonRemoval(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove no selected component"""
        component_url = self._tester.url + "/admin/ticket/components"
        tc.go(component_url)
        tc.submit('remove', formname='component_table')
        tc.find('No component selected')


class TestAdminComponentDefault(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin set default component"""
        name = "DefaultComponent"
        self._tester.create_component(name)
        component_url = self._tester.url + "/admin/ticket/components"
        tc.go(component_url)
        tc.formvalue('component_table', 'default', name)
        tc.submit('apply')
        tc.find('type="radio" name="default" checked="checked" value="%s"' % \
                name)
        tc.go(self._tester.url + '/newticket')
        tc.find('<option selected="selected" value="%s">%s</option>'
                % (name, name))
        # Test the "Clear default" button
        self._testenv.set_config('ticket', 'allowed_empty_fields', 'component')
        tc.go(component_url)
        tc.submit('clear', formname='component_table')
        tc.notfind('type="radio" name="default" checked="checked" value=".+"')
        self._tester.create_ticket()
        tc.find('<th class="missing" id="h_component">\s*Component:\s*</th>'
                '\s*<td headers="h_component">\s*</td>')
        self._testenv.remove_config('ticket', 'allowed_empty_fields')


class TestAdminComponentDetail(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin component detail"""
        name = "DetailComponent"
        self._tester.create_component(name)
        component_url = self._tester.url + "/admin/ticket/components"
        tc.go(component_url)
        tc.follow(name)
        desc = 'Some component description'
        tc.formvalue('edit', 'description', desc)
        tc.submit('cancel')
        tc.url(component_url + '$')
        tc.follow(name)
        tc.notfind(desc)


class TestAdminComponentNoneDefined(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """The table should be hidden and help text shown when there are no
        components defined (#11103)."""
        from trac.ticket import model
        env = self._testenv.get_trac_environment()
        components = list(model.Component.select(env))
        self._tester.go_to_admin()
        tc.follow(r"\bComponents\b")

        try:
            for comp in components:
                tc.formvalue('component_table', 'sel', comp.name)
            tc.submit('remove')
            tc.notfind('<table class="listing" id="complist">')
            tc.find("As long as you don't add any items to the list, this "
                    "field[ \t\n]*will remain completely hidden from the "
                    "user interface.")
        finally:
            for comp in components:
                self._tester.create_component(comp.name, comp.owner,
                                              comp.description)


class TestAdminMilestone(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create milestone"""
        self._tester.create_milestone()


class TestAdminMilestoneAuthorization(AuthorizationTestCaseSetup):
    def runTest(self):
        """Check permissions required to access the Ticket Milestone
        panel."""
        self.test_authorization('/admin/ticket/milestones', 'TICKET_ADMIN',
                                "Manage Milestones")


class TestAdminMilestoneSpace(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create milestone with a space"""
        self._tester.create_milestone('Milestone 1')


class TestAdminMilestoneDuplicates(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create duplicate milestone"""
        name = "DuplicateMilestone"
        self._tester.create_milestone(name)
        milestone_url = self._tester.url + "/admin/ticket/milestones"
        tc.go(milestone_url)
        tc.url(milestone_url)
        tc.formvalue('addmilestone', 'name', name)
        tc.submit()
        tc.notfind(internal_error)
        tc.find('Milestone "%s" already exists, please choose '
                'another name.' % name)
        tc.notfind('%s')


class TestAdminMilestoneListing(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin milestone listing."""
        name1 = self._tester.create_milestone()
        self._tester.create_ticket(info={'milestone': name1})
        name2 = self._tester.create_milestone()

        milestone_url = self._tester.url + '/admin/ticket/milestones'
        tc.go(milestone_url)
        tc.url(milestone_url)
        tc.find(r'<a href="/admin/ticket/milestones/%(name)s">%(name)s</a>'
                % {'name': name1})
        m1_query_link = r'<a href="/query\?group=status&amp;' \
                        r'milestone=%(name)s">1</a>' % {'name': name1}
        tc.find(m1_query_link)
        tc.find(r'<a href="/admin/ticket/milestones/%(name)s">%(name)s</a>'
                % {'name': name2})
        tc.notfind(r'<a href="/query\?group=status&amp;milestone=%(name)s">'
                   r'0</a>' % {'name': name2})

        apply_submit = '<input type="submit" name="apply" ' \
                       'value="Apply changes" />'
        clear_submit = '<input type="submit"[ \t\n]+title="Clear default ' \
                       'ticket milestone and default retargeting milestone"' \
                       '[ \t\n]+name="clear" value="Clear defaults" />'
        tc.find(apply_submit)
        tc.find(clear_submit)
        tc.find('<input type="radio" name="ticket_default" value="%(name)s"/>'
                % {'name': name1})
        tc.find('<input type="radio" name="retarget_default" value="%(name)s"/>'
                % {'name': name1})

        # TICKET_ADMIN is required to change the ticket default and retarget
        # default configuration options. TICKET_VIEW is required for the
        # milestone tickets query link to be present.
        try:
            self._testenv.grant_perm('user', 'MILESTONE_ADMIN')
            self._testenv.revoke_perm('anonymous', 'TICKET_VIEW')
            self._tester.go_to_front()
            self._tester.logout()
            self._tester.login('user')
            tc.go(milestone_url)
            tc.notfind(apply_submit)
            tc.notfind(clear_submit)
            tc.find('<input type="radio" name="ticket_default" '
                    'disabled="disabled" value="%(name)s"/>' % {'name': name1})
            tc.find('<input type="radio" name="retarget_default" '
                    'disabled="disabled" value="%(name)s"/>' % {'name': name1})
            tc.notfind(m1_query_link)
        finally:
            self._testenv.revoke_perm('user', 'MILESTONE_ADMIN')
            self._testenv.grant_perm('anonymous', 'TICKET_VIEW')
            self._tester.logout()
            self._tester.login('admin')


class TestAdminMilestoneDetail(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin modify milestone details"""
        name = self._tester.create_milestone()

        milestone_url = self._tester.url + '/admin/ticket/milestones'
        def go_to_milestone_detail():
            tc.go(milestone_url)
            tc.url(milestone_url)
            tc.follow(name)
            tc.url(milestone_url + '/' + name)

        # Modify the details of the milestone
        go_to_milestone_detail()
        tc.formvalue('edit', 'due', True)
        tc.formvalue('edit', 'description', 'Some description.')
        tc.submit('save')
        tc.url(milestone_url)

        # Milestone is not closed
        self._tester.go_to_roadmap()
        tc.find(name)

        # Cancel more modifications and modification are not saved
        go_to_milestone_detail()
        tc.formvalue('edit', 'description', '~~Some other description.~~')
        tc.submit('cancel')
        tc.url(milestone_url)
        self._tester.go_to_roadmap()
        tc.find('Some description.')
        tc.follow(name)
        tc.find('Some description.')

        # Milestone is readonly when user doesn't have MILESTONE_MODIFY
        self._tester.logout()
        self._testenv.grant_perm('user', 'TICKET_ADMIN')
        self._tester.login('user')
        go_to_milestone_detail()
        try:
            tc.find(r'<input[^>]+id="name"[^>]+readonly="readonly"')
            tc.find(r'<input[^>]+id="due"[^>]+disabled="disabled"')
            tc.find(r'<input[^>]+id="duedate"[^>]+readonly="readonly"')
            tc.find(r'<input[^>]+id="completed"[^>]+disabled="disabled"')
            tc.find(r'<input[^>]+id="completeddate"[^>]+readonly="readonly"')
            tc.find(r'<textarea[^>]+id="description"[^>]+readonly="readonly"')
            tc.find(r'<input[^>]+name="save"[^>]+disabled="disabled"')
            tc.submit('cancel', 'edit')
            tc.url(milestone_url)
        finally:
            self._tester.logout()
            self._testenv.revoke_perm('user', 'TICKET_ADMIN')
            self._tester.login('admin')


class TestAdminMilestoneDue(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin milestone duedate"""
        name = "DueMilestone"
        duedate = datetime_now(tz=utc)
        duedate_string = format_datetime(duedate, tzinfo=utc,
                                         locale=locale_en)
        self._tester.create_milestone(name, due=duedate_string)
        tc.find(duedate_string)


class TestAdminMilestoneDetailDue(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin modify milestone duedate on detail page"""
        name = "DetailDueMilestone"
        # Create a milestone
        self._tester.create_milestone(name)

        # Modify the details of the milestone
        milestone_url = self._tester.url + "/admin/ticket/milestones"
        tc.go(milestone_url)
        tc.url(milestone_url)
        tc.follow(name)
        tc.url(milestone_url + '/' + name)
        duedate = datetime_now(tz=utc)
        duedate_string = format_datetime(duedate, tzinfo=utc,
                                         locale=locale_en)
        tc.formvalue('edit', 'due', True)
        tc.formvalue('edit', 'duedate', duedate_string)
        tc.submit('save')
        tc.url(milestone_url + '$')
        tc.find(name + '(<[^>]*>|\\s)*'+ duedate_string, 's')


class TestAdminMilestoneDetailRename(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin rename milestone"""
        name1 = self._tester.create_milestone()
        name2 = random_unique_camel()
        tid = self._tester.create_ticket(info={'milestone': name1})
        milestone_url = self._tester.url + '/admin/ticket/milestones'

        self._tester.go_to_url(milestone_url)
        tc.follow(name1)
        tc.url(milestone_url + '/' + name1)
        tc.formvalue('edit', 'name', name2)
        tc.submit('save')

        tc.find(r"Your changes have been saved\.")
        tc.find(r"\b%s\b" % name2)
        tc.notfind(r"\b%s\b" % name1)
        self._tester.go_to_ticket(tid)
        tc.find('<a class="milestone" href="/milestone/%(name)s" '
                'title="No date set">%(name)s</a>' % {'name': name2})
        tc.find('<th class="trac-field-milestone">Milestone:</th>[ \t\n]+'
                '<td>[ \t\n]+<span class="trac-field-old">%s</span>'
                '[ \t\n]+→[ \t\n]+'
                '<span class="trac-field-new">%s</span>[ \t\n]+</td>'
                % (name1, name2))
        tc.find("Milestone renamed")


class TestAdminMilestoneCompleted(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin milestone completed"""
        name = "CompletedMilestone"
        self._tester.create_milestone(name)
        milestone_url = self._tester.url + "/admin/ticket/milestones"
        tc.go(milestone_url)
        tc.url(milestone_url)
        tc.follow(name)
        tc.url(milestone_url + '/' + name)
        tc.formvalue('edit', 'completed', True)
        tc.submit('save')
        tc.url(milestone_url + "$")


class TestAdminMilestoneCompletedFuture(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin milestone completed in the future"""
        name = "CompletedFutureMilestone"
        self._tester.create_milestone(name)
        milestone_url = self._tester.url + "/admin/ticket/milestones"
        tc.go(milestone_url)
        tc.url(milestone_url)
        tc.follow(name)
        tc.url(milestone_url + '/' + name)
        tc.formvalue('edit', 'completed', True)
        cdate = datetime_now(tz=utc) + timedelta(days=2)
        cdate_string = format_date(cdate, tzinfo=localtz, locale=locale_en)
        tc.formvalue('edit', 'completeddate', cdate_string)
        tc.submit('save')
        tc.find('Completion date may not be in the future')
        # And make sure it wasn't marked as completed.
        self._tester.go_to_roadmap()
        tc.find(name)


class TestAdminMilestoneRemove(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove milestone"""
        name = "MilestoneRemove"
        self._tester.create_milestone(name)
        tid = self._tester.create_ticket(info={'milestone': name})
        milestone_url = self._tester.url + '/admin/ticket/milestones'

        tc.go(milestone_url)
        tc.formvalue('milestone_table', 'sel', name)
        tc.submit('remove')

        tc.url(milestone_url + '$')
        tc.notfind(name)
        self._tester.go_to_ticket(tid)
        tc.find('<th class="missing" id="h_milestone">'
                '[ \t\n]*Milestone:[ \t\n]*</th>')
        tc.find('<th class="trac-field-milestone">Milestone:</th>[ \t\n]+'
                '<td>[ \t\n]+'
                '<span class="trac-field-deleted">%s</span>'
                '[ \t\n]+</td>' % name)
        tc.find("Milestone deleted")


class TestAdminMilestoneRemoveMulti(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove multiple milestones"""
        name = "MultiRemoveMilestone"
        count = 3
        for i in xrange(count):
            self._tester.create_milestone("%s%s" % (name, i))
        milestone_url = self._tester.url + '/admin/ticket/milestones'
        tc.go(milestone_url)
        tc.url(milestone_url + '$')
        for i in xrange(count):
            tc.find("%s%s" % (name, i))
        for i in xrange(count):
            tc.formvalue('milestone_table', 'sel', "%s%s" % (name, i))
        tc.submit('remove')
        tc.url(milestone_url + '$')
        for i in xrange(count):
            tc.notfind("%s%s" % (name, i))


class TestAdminMilestoneNonRemoval(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove no selected milestone"""
        milestone_url = self._tester.url + "/admin/ticket/milestones"
        tc.go(milestone_url)
        tc.submit('remove', formname='milestone_table')
        tc.find('No milestone selected')


class TestAdminMilestoneDefaults(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin set default ticket milestone, default retarget milestone
        and clear defaults."""
        def clear_defaults():
            # Test the "Clear default" button
            tc.go(milestone_url)
            tc.submit('clear', formname='milestone_table')
            tc.notfind('type="radio" name="ticket_default" '
                       'checked="checked" value=".+"')
            tc.notfind('type="radio" name="retarget_default" '
                       'checked="checked value=".+""')
            self._tester.go_to_ticket(tid)
            tc.find('<th class="missing" id="h_milestone">[ \t\n]+'
                    'Milestone:[ \t\n]+</th>[ \t\n]+'
                    '(?!<td headers="h_milestone">)')
            self._tester.go_to_milestone(mid2)
            tc.submit(formname='deletemilestone')
            tc.notfind('<option selected="selected" value="%s">%s</option>'
                       % (mid1, mid1))

        milestone_url = self._tester.url + "/admin/ticket/milestones"
        tid = self._tester.create_ticket()
        mid1 = self._tester.create_milestone()
        mid2 = self._tester.create_milestone()
        self._tester.create_ticket(info={'milestone': mid2})

        # Set default ticket milestone
        tc.go(milestone_url)
        tc.formvalue('milestone_table', 'ticket_default', mid1)
        tc.submit('apply')
        tc.find('type="radio" name="ticket_default"'
                ' checked="checked" value="%s"' % mid1)
        tc.notfind('type="radio" name="retarget_default"'
                   ' checked="checked" value=".+"')
        # verify it is the default on the newticket page.
        tc.go(self._tester.url + '/newticket')
        tc.find('<option selected="selected" value="%s">%s</option>'
                % (mid1, mid1))
        clear_defaults()

        # Set default retarget to milestone
        tc.go(milestone_url)
        tc.formvalue('milestone_table', 'retarget_default', mid1)
        tc.submit('apply')
        tc.find('type="radio" name="retarget_default"'
                ' checked="checked" value="%s"' % mid1)
        tc.notfind('type="radio" name="ticket_default"'
                   ' checked="checked" value=".+"')
        # verify it is the default on the confirm delete page.
        self._tester.go_to_milestone(mid2)
        tc.submit(formname='deletemilestone')
        tc.find('<option selected="selected" value="%s">%s</option>'
                % (mid1, mid1))
        clear_defaults()

        # Set both
        tc.go(milestone_url)
        tc.formvalue('milestone_table', 'ticket_default', mid1)
        tc.formvalue('milestone_table', 'retarget_default', mid1)
        tc.submit('apply')
        tc.find('type="radio" name="ticket_default"'
                ' checked="checked" value="%s"' % mid1)
        tc.find('type="radio" name="retarget_default"'
                ' checked="checked" value="%s"' % mid1)
        # verify it is the default on the newticket page.
        tc.go(self._tester.url + '/newticket')
        tc.find('<option selected="selected" value="%s">%s</option>'
                % (mid1, mid1))
        # verify it is the default on the confirm delete page.
        self._tester.go_to_milestone(mid2)
        tc.submit(formname='deletemilestone')
        tc.find('<option selected="selected" value="%s">%s</option>'
                % (mid1, mid1))
        clear_defaults()

        #Set neither
        tc.go(milestone_url)
        tc.submit('apply', formname='milestone_table')
        tc.notfind('type="radio" name="retarget_default"'
                   ' checked="checked" value=".+"')
        tc.notfind('type="radio" name="ticket_default"'
                   ' checked="checked" value=".+"')
        # verify no default on the newticket page.
        tc.go(self._tester.url + '/newticket')
        tc.find('<th class="missing" id="h_milestone">[ \t\n]+'
                'Milestone:[ \t\n]+</th>[ \t\n]+'
                '(?!<td headers="h_milestone">)')
        # verify none selected on the confirm delete page.
        self._tester.go_to_milestone(mid2)
        tc.submit(formname='deletemilestone')
        tc.notfind('<option selected="selected" value="%s">%s</option>'
                   % (mid1, mid1))


class TestAdminPriority(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create priority"""
        self._tester.create_priority()


class TestAdminPriorityAuthorization(AuthorizationTestCaseSetup):
    def runTest(self):
        """Check permissions required to access the Ticket Priority
        panel."""
        self.test_authorization('/admin/ticket/priority', 'TICKET_ADMIN',
                                "Manage Priorities")


class TestAdminPriorityDuplicates(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create duplicate priority"""
        name = "DuplicatePriority"
        self._tester.create_priority(name)
        self._tester.create_priority(name)
        tc.find('Priority %s already exists' % name)


class TestAdminPriorityModify(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin modify priority"""
        name = "ModifyPriority"
        self._tester.create_priority(name)
        priority_url = self._tester.url + '/admin/ticket/priority'
        tc.go(priority_url)
        tc.url(priority_url + '$')
        tc.find(name)
        tc.follow(name)
        tc.formvalue('edit', 'name', name * 2)
        tc.submit('save')
        tc.url(priority_url + '$')
        tc.find(name * 2)


class TestAdminPriorityRemove(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove priority"""
        name = "RemovePriority"
        self._tester.create_priority(name)
        priority_url = self._tester.url + '/admin/ticket/priority'
        tc.go(priority_url)
        tc.url(priority_url + '$')
        tc.find(name)
        tc.formvalue('enumtable', 'sel', name)
        tc.submit('remove')
        tc.url(priority_url + '$')
        tc.notfind(name)


class TestAdminPriorityRemoveMulti(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove multiple priorities"""
        name = "MultiRemovePriority"
        count = 3
        for i in xrange(count):
            self._tester.create_priority("%s%s" % (name, i))
        priority_url = self._tester.url + '/admin/ticket/priority'
        tc.go(priority_url)
        tc.url(priority_url + '$')
        for i in xrange(count):
            tc.find("%s%s" % (name, i))
        for i in xrange(count):
            tc.formvalue('enumtable', 'sel', "%s%s" % (name, i))
        tc.submit('remove')
        tc.url(priority_url + '$')
        for i in xrange(count):
            tc.notfind("%s%s" % (name, i))


class TestAdminPriorityNonRemoval(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove no selected priority"""
        priority_url = self._tester.url + "/admin/ticket/priority"
        tc.go(priority_url)
        tc.submit('remove', formname='enumtable')
        tc.find('No priority selected')


class TestAdminPriorityDefault(AdminEnumDefaultTestCaseSetup):
    def runTest(self):
        """Admin default priority"""
        name = self._tester.create_priority()
        self.test_default('priority', name)


class TestAdminPriorityDetail(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin modify priority details"""
        name = "DetailPriority"
        # Create a priority
        self._tester.create_priority(name + '1')

        # Modify the details of the priority
        priority_url = self._tester.url + "/admin/ticket/priority"
        tc.go(priority_url)
        tc.url(priority_url + '$')
        tc.follow(name + '1')
        tc.url(priority_url + '/' + name + '1')
        tc.formvalue('edit', 'name', name + '2')
        tc.submit('save')
        tc.url(priority_url + '$')

        # Cancel more modifications
        tc.go(priority_url)
        tc.follow(name)
        tc.formvalue('edit', 'name', name + '3')
        tc.submit('cancel')
        tc.url(priority_url + '$')

        # Verify that only the correct modifications show up
        tc.notfind(name + '1')
        tc.find(name + '2')
        tc.notfind(name + '3')


class TestAdminPriorityRenumber(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin renumber priorities"""
        valuesRE = re.compile('<select name="value_([0-9]+)">', re.M)
        html = b.get_html()
        max_priority = max([int(x) for x in valuesRE.findall(html)])

        name = "RenumberPriority"
        self._tester.create_priority(name + '1')
        self._tester.create_priority(name + '2')
        priority_url = self._tester.url + '/admin/ticket/priority'
        tc.go(priority_url)
        tc.url(priority_url + '$')
        tc.find(name + '1')
        tc.find(name + '2')
        tc.formvalue('enumtable',
                     'value_%s' % (max_priority + 1), str(max_priority + 2))
        tc.formvalue('enumtable',
                     'value_%s' % (max_priority + 2), str(max_priority + 1))
        tc.submit('apply')
        tc.url(priority_url + '$')
        # Verify that their order has changed.
        tc.find(name + '2.*' + name + '1', 's')


class TestAdminPriorityRenumberDup(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin badly renumber priorities"""
        # Make the first priority the 2nd priority, and leave the 2nd priority
        # as the 2nd priority.
        priority_url = self._tester.url + '/admin/ticket/priority'
        tc.go(priority_url)
        tc.url(priority_url + '$')
        tc.formvalue('enumtable', 'value_1', '2')
        tc.submit('apply')
        tc.url(priority_url + '$')
        tc.find('Order numbers must be unique')


class TestAdminResolution(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create resolution"""
        self._tester.create_resolution()


class TestAdminResolutionAuthorization(AuthorizationTestCaseSetup):
    def runTest(self):
        """Check permissions required to access the Ticket Resolutions
        panel."""
        self.test_authorization('/admin/ticket/resolution', 'TICKET_ADMIN',
                                "Manage Resolutions")


class TestAdminResolutionDuplicates(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create duplicate resolution"""
        name = "DuplicateResolution"
        self._tester.create_resolution(name)
        self._tester.create_resolution(name)
        tc.find(escape('Resolution value "%s" already exists' % name))


class TestAdminResolutionDefault(AdminEnumDefaultTestCaseSetup):
    def runTest(self):
        """Admin default resolution"""
        name = self._tester.create_resolution()
        self.test_default('resolution', name)


class TestAdminSeverity(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create severity"""
        self._tester.create_severity()


class TestAdminSeverityAuthorization(AuthorizationTestCaseSetup):
    def runTest(self):
        """Check permissions required to access the Ticket Severities
        panel."""
        self.test_authorization('/admin/ticket/severity', 'TICKET_ADMIN',
                                "Manage Severities")


class TestAdminSeverityDuplicates(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create duplicate severity"""
        name = "DuplicateSeverity"
        self._tester.create_severity(name)
        self._tester.create_severity(name)
        tc.find(escape('Severity value "%s" already exists' % name))


class TestAdminSeverityDefault(AdminEnumDefaultTestCaseSetup):
    def runTest(self):
        """Admin default severity"""
        name = self._tester.create_severity()
        self.test_default('severity', name)


class TestAdminType(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create type"""
        self._tester.create_type()


class TestAdminTypeAuthorization(AuthorizationTestCaseSetup):
    def runTest(self):
        """Check permissions required to access the Ticket Types
        panel."""
        self.test_authorization('/admin/ticket/type', 'TICKET_ADMIN',
                                "Manage Ticket Types")


class TestAdminTypeDuplicates(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create duplicate type"""
        name = "DuplicateType"
        self._tester.create_type(name)
        self._tester.create_type(name)
        tc.find(escape('Type value "%s" already exists' % name))


class TestAdminTypeDefault(AdminEnumDefaultTestCaseSetup):
    def runTest(self):
        """Admin default type"""
        name = self._tester.create_type()
        self.test_default('type', name)


class TestAdminVersion(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create version"""
        self._tester.create_version()


class TestAdminVersionAuthorization(AuthorizationTestCaseSetup):
    def runTest(self):
        """Check permissions required to access the Versions panel."""
        self.test_authorization('/admin/ticket/versions', 'TICKET_ADMIN',
                                "Manage Versions")


class TestAdminVersionDuplicates(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create duplicate version"""
        name = "DuplicateVersion"
        self._tester.create_version(name)
        version_admin = self._tester.url + "/admin/ticket/versions"
        tc.go(version_admin)
        tc.url(version_admin)
        tc.formvalue('addversion', 'name', name)
        tc.submit()
        tc.notfind(internal_error)
        tc.find(escape('Version "%s" already exists.' % name))


class TestAdminVersionDetail(FunctionalTwillTestCaseSetup):
    # This is somewhat pointless... the only place to find the version
    # description is on the version details page.
    def runTest(self):
        """Admin version details"""
        name = "DetailVersion"
        self._tester.create_version(name)
        version_admin = self._tester.url + "/admin/ticket/versions"
        tc.go(version_admin)
        tc.url(version_admin)
        tc.follow(name)

        desc = 'Some version description.'
        tc.formvalue('edit', 'description', desc)
        tc.submit('save')
        tc.url(version_admin)
        tc.follow(name)
        tc.find(desc)


class TestAdminVersionDetailTime(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin version detail set time"""
        name = "DetailTimeVersion"
        self._tester.create_version(name)
        version_admin = self._tester.url + "/admin/ticket/versions"
        tc.go(version_admin)
        tc.url(version_admin)
        tc.follow(name)

        tc.formvalue('edit', 'time', '')
        tc.submit('save')
        tc.url(version_admin + '$')
        tc.find(name + '(<[^>]*>|\\s)*<[^>]* name="default" value="%s"'
                % name, 's')

        # Empty time value is not automatically populated.
        tc.follow(name)
        tc.find('<input type="text" id="releaseddate"[^>]*value=""')
        tc.submit('save', formname="edit")
        tc.url(version_admin + '$')
        tc.find(name + '(<[^>]*>|\\s)*<[^>]* name="default" value="%s"'
                % name, 's')


class TestAdminVersionDetailCancel(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin version details"""
        name = "DetailVersion"
        self._tester.create_version(name)
        version_admin = self._tester.url + "/admin/ticket/versions"
        tc.go(version_admin)
        tc.url(version_admin)
        tc.follow(name)

        desc = 'Some other version description.'
        tc.formvalue('edit', 'description', desc)
        tc.submit('cancel')
        tc.url(version_admin)
        tc.follow(name)
        tc.notfind(desc)


class TestAdminVersionRemove(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove version"""
        name = "VersionRemove"
        self._tester.create_version(name)
        version_url = self._tester.url + "/admin/ticket/versions"
        tc.go(version_url)
        tc.formvalue('version_table', 'sel', name)
        tc.submit('remove')
        tc.url(version_url + '$')
        tc.notfind(name)


class TestAdminVersionRemoveMulti(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove multiple versions"""
        name = "MultiRemoveVersion"
        count = 3
        for i in xrange(count):
            self._tester.create_version("%s%s" % (name, i))
        version_url = self._tester.url + '/admin/ticket/versions'
        tc.go(version_url)
        tc.url(version_url + '$')
        for i in xrange(count):
            tc.find("%s%s" % (name, i))
        for i in xrange(count):
            tc.formvalue('version_table', 'sel', "%s%s" % (name, i))
        tc.submit('remove')
        tc.url(version_url + '$')
        for i in xrange(count):
            tc.notfind("%s%s" % (name, i))


class TestAdminVersionNonRemoval(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin remove no selected version"""
        version_url = self._tester.url + "/admin/ticket/versions"
        tc.go(version_url)
        tc.submit('remove', formname='version_table')
        tc.find('No version selected')


class TestAdminVersionDefault(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin set default version"""
        name = "DefaultVersion"
        self._tester.create_version(name)
        version_url = self._tester.url + "/admin/ticket/versions"
        tc.go(version_url)
        tc.formvalue('version_table', 'default', name)
        tc.submit('apply')
        tc.find('type="radio" name="default" checked="checked" value="%s"' % \
                name)
        # verify it is the default on the newticket page.
        tc.go(self._tester.url + '/newticket')
        tc.find('<option selected="selected" value="%s">%s</option>'
                % (name, name))
        # Test the "Clear default" button
        tc.go(version_url)
        tc.submit('clear', formname='version_table')
        tc.notfind('type="radio" name="default" checked="checked" value=".+"')
        self._tester.create_ticket()
        tc.find('<th class="missing" id="h_version">[ \t\n]+'
                'Version:[ \t\n]+</th>[ \t\n]+'
                '(?!<td headers="h_version">)')


class RegressionTestRev5665(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Admin create version without release time (r5665)"""
        self._tester.create_version(releasetime='')


class RegressionTestTicket10772(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Test for regression of https://trac.edgewall.org/ticket/10772"""
        def find_prop(field, value=None):
            if value and field == 'type':
                tc.find(r'<span class="trac-%(field)s">\s*'
                        r'<a href="/query\?status=!closed&amp;'
                        r'%(field)s=%(value)s">\s*%(value)s\s*</a>\s*</span>'
                        % {'field': field, 'value': value})
            elif value and field == 'milestone':
                tc.find(r'<td headers="h_%(field)s">\s*'
                        r'<a class="%(field)s" href="/%(field)s/%(value)s" '
                        r'title=".+">\s*%(value)s\s*</a>\s*</td>'
                        % {'field': field, 'value': value})
            elif value:
                tc.find(r'<td headers="h_%(field)s">\s*'
                        r'<a href="/query\?status=!closed&amp;'
                        r'%(field)s=%(value)s">\s*%(value)s\s*</a>\s*</td>'
                        % {'field': field, 'value': value})
            else:
                tc.find(r'<td headers="h_%(field)s">\s*</td>'
                        % {'field': field})

        self._testenv.set_config('ticket', 'allowed_empty_fields',
                                 'component, milestone, priority, version')

        try:
            # TODO: use the //Clear default// buttons to clear these values
            self._tester.go_to_admin("Components")
            tc.submit('clear', formname='component_table')
            self._tester.go_to_admin("Milestones")
            tc.submit('clear', formname='milestone_table')
            self._tester.go_to_admin("Versions")
            tc.submit('clear', formname='version_table')
            self._tester.go_to_admin("Priorities")
            tc.formvalue('enumtable', 'default', 'major')
            tc.submit('apply')

            self._tester.create_ticket('ticket summary')

            find_prop('component')
            find_prop('milestone')
            find_prop('priority', 'major')
            find_prop('version')

            self._testenv.set_config('ticket', 'allowed_empty_fields', '')
            self._tester.go_to_admin("Components")
            tc.formvalue('component_table', 'default', 'component2')
            tc.submit('apply')
            self._tester.go_to_admin("Milestones")
            tc.formvalue('milestone_table', 'ticket_default', 'milestone2')
            tc.submit('apply')
            self._tester.go_to_admin("Priorities")
            tc.formvalue('enumtable', 'default', 'minor')
            tc.submit('apply')
            self._tester.go_to_admin("Versions")
            tc.formvalue('version_table', 'default', '2.0')
            tc.submit('apply')
            self._tester.go_to_admin("Ticket Types")
            tc.formvalue('enumtable', 'default', 'task')
            tc.submit('apply')

            self._tester.create_ticket('ticket summary')

            find_prop('component', 'component2')
            find_prop('milestone', 'milestone2')
            find_prop('priority', 'minor')
            find_prop('version', '2.0')
            find_prop('type', 'task')
        finally:
            self._testenv.remove_config('ticket', 'allowed_empty_fields')


class RegressionTestTicket11618(FunctionalTwillTestCaseSetup):
    def runTest(self):
        """Test for regression of https://trac.edgewall.org/ticket/11618
        fix for malformed `readonly="True"` attribute in milestone admin page
        """
        name = "11618Milestone"
        self._tester.create_milestone(name)
        try:
            self._testenv.grant_perm('user', 'TICKET_ADMIN')
            self._tester.go_to_front()
            self._tester.logout()
            self._tester.login('user')
            tc.go(self._tester.url + "/admin/ticket/milestones/" + name)
            tc.notfind('No administration panels available')
            tc.find(' readonly="readonly"')
            tc.notfind(' readonly="True"')
        finally:
            self._testenv.revoke_perm('user', 'TICKET_ADMIN')
            self._tester.go_to_front()
            self._tester.logout()
            self._tester.login('admin')


def functionalSuite(suite=None):
    if not suite:
        import trac.tests.functional
        suite = trac.tests.functional.functionalSuite()
    suite.addTest(TestAdminComponent())
    suite.addTest(TestAdminComponentAuthorization())
    suite.addTest(TestAdminComponentDuplicates())
    suite.addTest(TestAdminComponentRemoval())
    suite.addTest(TestAdminComponentNonRemoval())
    suite.addTest(TestAdminComponentDefault())
    suite.addTest(TestAdminComponentDetail())
    suite.addTest(TestAdminComponentNoneDefined())
    suite.addTest(TestAdminMilestone())
    suite.addTest(TestAdminMilestoneAuthorization())
    suite.addTest(TestAdminMilestoneSpace())
    suite.addTest(TestAdminMilestoneDuplicates())
    suite.addTest(TestAdminMilestoneListing())
    suite.addTest(TestAdminMilestoneDetail())
    suite.addTest(TestAdminMilestoneDue())
    suite.addTest(TestAdminMilestoneDetailDue())
    suite.addTest(TestAdminMilestoneDetailRename())
    suite.addTest(TestAdminMilestoneCompleted())
    suite.addTest(TestAdminMilestoneCompletedFuture())
    suite.addTest(TestAdminMilestoneRemove())
    suite.addTest(TestAdminMilestoneRemoveMulti())
    suite.addTest(TestAdminMilestoneNonRemoval())
    suite.addTest(TestAdminMilestoneDefaults())
    suite.addTest(TestAdminPriority())
    suite.addTest(TestAdminPriorityAuthorization())
    suite.addTest(TestAdminPriorityModify())
    suite.addTest(TestAdminPriorityRemove())
    suite.addTest(TestAdminPriorityRemoveMulti())
    suite.addTest(TestAdminPriorityNonRemoval())
    suite.addTest(TestAdminPriorityDefault())
    suite.addTest(TestAdminPriorityDetail())
    suite.addTest(TestAdminPriorityRenumber())
    suite.addTest(TestAdminPriorityRenumberDup())
    suite.addTest(TestAdminResolution())
    suite.addTest(TestAdminResolutionAuthorization())
    suite.addTest(TestAdminResolutionDuplicates())
    suite.addTest(TestAdminResolutionDefault())
    suite.addTest(TestAdminSeverity())
    suite.addTest(TestAdminSeverityAuthorization())
    suite.addTest(TestAdminSeverityDuplicates())
    suite.addTest(TestAdminSeverityDefault())
    suite.addTest(TestAdminType())
    suite.addTest(TestAdminTypeAuthorization())
    suite.addTest(TestAdminTypeDuplicates())
    suite.addTest(TestAdminTypeDefault())
    suite.addTest(TestAdminVersion())
    suite.addTest(TestAdminVersionAuthorization())
    suite.addTest(TestAdminVersionDuplicates())
    suite.addTest(TestAdminVersionDetail())
    suite.addTest(TestAdminVersionDetailTime())
    suite.addTest(TestAdminVersionDetailCancel())
    suite.addTest(TestAdminVersionRemove())
    suite.addTest(TestAdminVersionRemoveMulti())
    suite.addTest(TestAdminVersionNonRemoval())
    suite.addTest(TestAdminVersionDefault())
    suite.addTest(RegressionTestRev5665())
    suite.addTest(RegressionTestTicket10772())
    suite.addTest(RegressionTestTicket11618())

    return suite


test_suite = functionalSuite


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