# -*- encoding: utf-8 -*-
#   Copyright 2008-2009 Agile42 GmbH, Berlin (Germany)
#   Copyright 2007 Andrea Tomasini <andrea.tomasini_at_agile42.com>
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#   
#   Author: 
#       - Andrea Tomasini <andrea.tomasini_at_agile42.com>
#       - Felix Schwarz <felix.schwarz__at__agile42.com>

from datetime import datetime, timedelta
import re
from StringIO import StringIO

from trac.util.datefmt import to_datetime, localtz
from trac.tests.functional.better_twill import twill_write_html
from trac.tests.functional import tc
from twill.errors import TwillAssertionError

from agilo.csv_import.csv_file import CSVFile
from agilo.scrum import BACKLOG_URL
from agilo.scrum.backlog import BacklogModelManager
from agilo.scrum.sprint import SprintModelManager
from agilo.utils import BacklogType, Key, Status, Type
from agilo.test import TestEnvHelper
from agilo.ticket.model import AgiloTicketModelManager

from agilo.test import Usernames
from agilo.test.functional import AgiloTestCase

# regexp to check the buttons disabled
button_disabled = "<input.*?name=\"%s\".*?disabled=\"disabled\".*?/>"
button_enabled = "<input.*?name=\"%s\".*?[^disabled=\"disabled\"]?.*?/>"


class TestBacklog(AgiloTestCase):
    
    def _test_backlog_list(self):
        """Checks that the URL exists and is showing the two backlogs"""
        # Anonymous user by default has no BACKLOG_VIEW right
        self._tester.logout()
        page_url = self._tester.url + BACKLOG_URL
        tc.go(page_url)
        tc.url(page_url)
        tc.code(200)
        # The page should not show the Sprint Backlog and Product Backlog
        tc.notfind('Product Backlog</a>')
        tc.notfind('Sprint Backlog')
        # Now login as Team Member with BACKLOG_VIEW rights
        self._tester.login_as(Usernames.team_member)
        tc.go(page_url)
        tc.url(page_url)
        tc.code(200)
        # The page should not show the Sprint Backlog and Product Backlog
        tc.find('Product Backlog</a>')
        tc.find('Sprint Backlog')
        
    
    def _create_a_product_backlog(self):
        """Utility to create a Product Backlog"""
        teh = TestEnvHelper(env=self.env)
        req1 = teh.create_ticket(Type.REQUIREMENT, props={Key.BUSINESS_VALUE: '2000'})
        us1 = teh.create_ticket(Type.USER_STORY, props={Key.STORY_PRIORITY: 'Linear',
                                                        Key.STORY_POINTS: '13'})
        us2 = teh.create_ticket(Type.USER_STORY, props={Key.STORY_PRIORITY: 'Mandatory',
                                                        Key.STORY_POINTS: '5'})
        req1.link_to(us1)
        req1.link_to(us2)
        req2 = teh.create_ticket(Type.REQUIREMENT, props={Key.BUSINESS_VALUE: '1200'})
        us3 = teh.create_ticket(Type.USER_STORY, props={Key.STORY_PRIORITY: 'Mandatory'})
        req2.link_to(us3)
        # Raise the twill tester ticket count
        self._tester.ticketcount += 5
        return req1.get_id(), req2.get_id(), us1.get_id(), us2.get_id(), us3.get_id()
    
    def _create_a_sprint_backlog(self, name=None, start=None, team=None):
        """Creates a sample sprint backlog and returns the tickets ids"""
        if name is None:
            name = 'Testsprint'
        teh = TestEnvHelper(env=self.env)
        sprint = teh.create_sprint(name, start=start, team=team)
        # Now create some tickets and plan them for the sprint
        us1 = teh.create_ticket(Type.USER_STORY,
                                props={Key.SPRINT: sprint.name,
                                       Key.STORY_POINTS: '13'})
        t1 = teh.create_ticket(Type.TASK,
                               props={Key.SPRINT: sprint.name,
                                      Key.REMAINING_TIME: '6'})
        t2 = teh.create_ticket(Type.TASK,
                               props={Key.SPRINT: sprint.name,
                                      Key.REMAINING_TIME: '4'})
        t3 = teh.create_ticket(Type.TASK,
                               props={Key.SPRINT: sprint.name})
        us1.link_to(t1)
        us1.link_to(t2)
        # Raise the twill tester ticket count
        self._tester.ticketcount += 4
        return us1.get_id(), t1.get_id(), t2.get_id(), t3.get_id()
        
    def _test_product_backlog(self):
        """Checks that the Product Backlog shows the created tickets"""
        self._tester.login_as(Usernames.product_owner)
        self._create_a_product_backlog()
        self._tester.navigate_to_product_backlog()
        # Find the requirement and the user stories on the page
        tc.find('Requirement n.1')
        tc.find('Story n.2')
        tc.find('Story n.3')
        tc.find('Requirement n.4')
        tc.find('Story n.5')
        
    def _test_product_backlog_save(self):
        """Tests the sorting in the product Backlog view"""
        self._tester.logout()
        self._tester.navigate_to_product_backlog(should_fail=True)
        # without being logged there sort button should be disabled
        # login as PO
        self._tester.login_as(Usernames.product_owner)
        # Should stay in the same page
        self._tester.navigate_to_product_backlog()
        
        # Now save the tickets
        tc.formvalue('backlog_form', 'save', 'Save')
        tc.submit('save')
        tc.code(200)
        
    def _test_product_backlog_sort(self):
        """Tests the sorting in the product Backlog view"""
        self._tester.logout()
        self._tester.navigate_to_product_backlog(should_fail=True)
        # login as PO
        self._tester.login_as(Usernames.product_owner)
        # Creates some tickets to sort
        r1_id, r2_id, u1_id, u2_id, u3_id = \
            self._create_a_product_backlog()
        # Should stay in the same page
        self._tester.navigate_to_product_backlog()
        tc.notfind(button_disabled % 'sort')
        # Now sort the tickets
        tc.formvalue('backlog_form', 'sort', 'click')
        tc.submit('sort')
        tc.code(200)
        
    def _test_sprint_backlog_view(self):
        """Test the sprint backlog for a sprint"""
        now = to_datetime(None)
        sprint = self.smm.create(name="SprintBacklogViewSprint", 
                                 start=now, duration=20)
        # Now navigate to the Sprint Backlog for the sprint
        self._tester.navigate_to_sprint_backlog(sprint.name)
    
    def _test_confirm_button_at_beginning_of_sprint(self):
        """Tests that the confirm button is there only the first day 
        of the sprint"""
        self._tester.login_as(Usernames.admin)
        # first we need to create a team to make sure the Sprint will have a 
        # capacity and a committable time
        self._tester.create_new_team('SprintTeam1')
        self._tester.add_member_to_team('SprintTeam1', 'member1')
        self._tester.add_member_to_team('SprintTeam1', 'member2')
        # Now navigate the Sprint Backlog and set the team for it
        self._create_a_sprint_backlog('SprintNotConfirm',
                                      start=to_datetime(None) - timedelta(days=3),
                                      team='SprintTeam1')
        # Now navigate the the sprint backlog and check that the 
        # button is not there cause by default the sprint is created 3 
        # days ago :)
        self._tester.login_as(Usernames.scrum_master)
        self._tester.navigate_to_sprint_backlog('SprintNotConfirm')
        tc.notfind('<input type="submit" name="confirm"')
        # Now create a sprint with date now... the confirm should be 
        # there
        self._tester.login_as(Usernames.admin)
        self._create_a_sprint_backlog('SprintConfirm', 
                                      start=to_datetime(None), 
                                      team='SprintTeam1')
        self._tester.login_as(Usernames.scrum_master)
        self._tester.navigate_to_sprint_backlog('SprintConfirm')
        tc.find('<input type="submit" name="confirm"')
        # Now try to confirm the commitment
        tc.fv('backlog_form', 'confirm', 'click')
        tc.submit('confirm')
        tc.code(200)
        # now navigate to the team statistics page and check that the commitment
        # has been stored for the current sprint.
        self._tester.go_to_team_page('SprintTeam1')
        # There should be a 6 + 4h of commitment
        tc.find(r'<td class="sprint">.+SprintConfirm')
        tc.find(r'</td><td>10.0h</td><td>13.0</td>')
        
    def _test_product_backlog_with_planned_stories(self):
        """
        Tests that stories that are planned for a specific sprint
        are not appearing in the Product Backlog. see #327
        """
        self._tester.login_as(Usernames.product_owner)
        # Make sure there is no ticket in the DB
        self._tester.delete_all_tickets()
        s = self.smm.create(name="Sprint One", 
                            start=to_datetime(None), 
                            duration=20, 
                            milestone="milestone1")
        # create a requirement and two dependent stories
        r1_id, r2_id, u1_id, u2_id, u3_id = \
            self._create_a_product_backlog()
        # Check that the backlog contains the three tickets
        b = self.bmm.get(name='Product Backlog')
        self.assertEqual(b.count(), 5)
        # Navigate to product backlog and check that all three items are there
        self._tester.navigate_to_product_backlog()
        tc.find('Requirement n.1')
        tc.find('Story n.2')
        tc.find('Story n.3')
        tc.find('Requirement n.4')
        tc.find('Story n.5')
        
        # set Sprint of two stories
        t = self.tmm.get(tkt_id=u1_id)
        t['sprint'] = s.name
        self.tmm.save(t, author=Usernames.product_owner, 
                      comment='Planned for sprint')
        t = self.tmm.get(tkt_id=u3_id)
        t['sprint'] = s.name
        self.tmm.save(t, author=Usernames.product_owner, 
                      comment='Planned for sprint')
        # Now navigate to the product backlog and check that the 
        # planned story is not there Anymore
        self._tester.navigate_to_product_backlog()
        tc.find('Requirement n.1')
        tc.notfind('Story n.2')
        tc.find('Story n.3')
        tc.find('Requirement n.4')
        tc.notfind('Story n.5')
        
    # TODO: This tests is not working anymore because the filtering is done via
    # Javascript and JSON, so twill can't test that anylonger, we need windmill
    def _test_show_hide_tickets(self):
        """
        Tests if the buttons for showing/hiding tickets are displayed correctly
        """
        self._tester.login_as(Usernames.product_owner)
        self._create_a_product_backlog()
        self._tester.navigate_to_product_backlog()
        # on product backlogs we don't display buttons
        # CHANGED: now we show the my tickets also in the other backlogs
        # could make sense for example if a team of product owners is using
        # the backlog to organize internal work, or a team is using a Bug Backlog
        tc.find('display_form')

        def _find_tasks(find=[], notfind=[]):
            try:
                for f in find:
                    is_visible = self._tester.is_ticket_visible_in_backlog(f)
                    self.assertTrue(is_visible, "Did not find Task %d" % f)
                for f in notfind:
                    is_visible = self._tester.is_ticket_visible_in_backlog(f)
                    self.assertFalse(is_visible, "Task %d is not hidden (or is missing)" % f)
            except (AssertionError, TwillAssertionError), e:
                filename = twill_write_html()
                args = e.args + (filename,)
                raise TwillAssertionError(*args)
        
        # Create the team
        teh = TestEnvHelper(self.env)
        team_member = teh.create_member(Usernames.team_member, 
                                        team='Team')
        team_member2 = teh.create_member(Usernames.second_team_member, 
                                         team='Team')
        # Create sprint and a couple of tickets
        self._tester.login_as(Usernames.team_member)
        sprint_name = "ShowHideTasksInBacklogSprint"
        sprint = teh.create_sprint(name=sprint_name, team='Team')
        self.assertEqual('Team', sprint.team.name)
        t1 = self._tester.create_new_agilo_task('Task 1', 
                                                sprint=sprint_name)
        t2 = self._tester.create_new_agilo_task('Task 2', 
                                                sprint=sprint_name, 
                                                owner=team_member.name)
        t3 = self._tester.create_new_agilo_task('Task 3', 
                                                sprint=sprint_name, 
                                                owner=team_member2.name)
        t4 = self._tester.create_new_agilo_task('Task 4', 
                                                sprint=sprint_name, 
                                                owner=team_member.name)
        ticket = self.tmm.get(tkt_id=t4)
        ticket[Key.STATUS] = Status.CLOSED
        self.tmm.save(ticket, author=team_member.name, 
                      comment='no comment')

        # Now navigate to the Sprint Backlog for the sprint and look 
        # for buttons
        self._tester.navigate_to_sprint_backlog(sprint.name)
        tc.find('display_form')
        # all tasks but t4 that is closed should be displayed
        _find_tasks(find=(t1, t2, t3))
        
        tc.fv('display_form', 'my_tickets', 'click')
        #tc.submit('my-tickets')
        # show only tasks by this user
        _find_tasks(find=(t2,), notfind=(t1, t3, t4))
        # reloading should show the same view
        self._tester.navigate_to_sprint_backlog(sprint.name)
        _find_tasks(find=(t2,), notfind=(t1, t3, t4))
        
        tc.fv('display_form', 'closed_tickets', sprint.name)
        #tc.submit('closed-tickets') # now show also the closed
        # show only tasks by this user which are not closed
        _find_tasks(find=(t2, t4), notfind=(t1, t3))
        
        tc.fv('display_form', 'my_tickets', 'click')
        #tc.submit('my-tickets')
        # show all tasks by everybody
        _find_tasks(find=(t1, t2, t3, t4))
        
        tc.fv('display_form', 'closed_tickets', sprint.name)
        #tc.submit('closed-tickets') # now show also the closed
        # show everything again
        _find_tasks(find=(t1, t2, t3), notfind=(t4,))
        
    def _test_remove_items_from_backlog(self):
        """Tests the removal of items from the Backlog"""
        self._tester.login_as(Usernames.scrum_master)
        sprint_name = 'RemoveItemsSprint'
        # Make sure there is no ticket in the DB
        self._tester.delete_all_tickets()
        us1_id, t1_id, t2_id, t3_id = \
            self._create_a_sprint_backlog(sprint_name)
        self._tester.navigate_to_sprint_backlog(sprint_name)
        # Find the requirement and the user stories on the page
        tc.find('Story n.1')
        tc.find('Task n.2')
        tc.find('Task n.3')
        tc.find('Task n.4')
        # Now Task3 is unlinked let's remove it
        tc.formvalue('backlog_form', 'chk_%s' % t3_id, 'on')
        tc.formvalue('backlog_form', 'remove', 'click')
        tc.submit('remove')
        # Now Task3 should not be there anymore
        tc.notfind('Task n.4')
        
    def _test_sorting_position_stability_in_product_backlog(self):
        """Tests the stability of the sorting after the changes of 
        some tickets in the Product Backlog"""
        self._tester.login_as(Usernames.product_owner)
        self._tester.delete_all_tickets()
        # Now creates the backlog and sort it
        ids = self._create_a_product_backlog()
        # Now sort and get the values of the positions
        self._tester.navigate_to_product_backlog()
        tc.fv('backlog_form', 'sort', 'click')
        tc.submit('sort')
        # Store positions
        positions = dict()
        for t_id in ids:
            positions[t_id] = self._tester.get_position_in_backlog(t_id,
                                                                   Key.PRODUCT_BACKLOG)
            self.assertNotEqual(positions[t_id], None)
        # Now change a ticket value and save the backlog
        tc.fv('backlog_form', 'col-%s-story_priority' % ids[3], 'Exciter')
        tc.fv('backlog_form', 'save', 'click')
        tc.submit('save')
        # Now check that positions are not changed
        for t_id, pos in positions.items():
            self.assertEqual(self._tester.get_position_in_backlog(t_id,
                                                                  Key.PRODUCT_BACKLOG),
                             pos)
        # Now resort and verify that position 2 and 3 are swapped, this relays on
        # the actually defined sorting criteria in for the Product Backlog (see config.py)
        tc.fv('backlog_form', 'sort', 'click')
        tc.submit('sort')
        self.assertEqual(self._tester.get_position_in_backlog(2, Key.PRODUCT_BACKLOG), 
                         positions[3])
        self.assertEqual(self._tester.get_position_in_backlog(3, Key.PRODUCT_BACKLOG),
                         positions[2])

    def runTest(self):
        # load some managers
        self.env = self._testenv.get_trac_environment()
        self.bmm = BacklogModelManager(self.env)
        self.smm = SprintModelManager(self.env)
        self.tmm = AgiloTicketModelManager(self.env)
        # make sure we are anonymous
        self._test_backlog_list()
        self._test_product_backlog()
        self._test_product_backlog_save()
        self._test_product_backlog_sort()
        self._test_sprint_backlog_view()
        self._test_product_backlog_with_planned_stories()
        # TODO: needs a windmil test now...
        #self._test_show_hide_tickets()
        self._test_remove_items_from_backlog()
        self._test_sorting_position_stability_in_product_backlog()
        # Remove Sprints and Milestones from the DB we created already
        # more than 5 here...
        self._tester.delete_sprints_and_milestones()
        self._test_confirm_button_at_beginning_of_sprint()
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()


class TestRtUspCalculation(AgiloTestCase):
    def runTest(self):
        # get the trac env for backend value checking
        env = self._testenv.get_trac_environment()
        teh = TestEnvHelper(env=env)
        self._tester.login_as(Usernames.admin)
        sprint_name = 'Scrummy'
        team_name = 'Scrummy Team'
        self._tester.create_new_team(team_name)
        self._tester.create_milestone('milestone2')
        self._tester.create_sprint_for_milestone('milestone2', sprint_name, 
                to_datetime(None) - timedelta(days=3), duration='30', team=team_name)
        self.assertNotEqual(None, SprintModelManager(env).get(name=sprint_name), 
                            "Sprint %s not created!" % team_name)
        story_id = self._tester.create_new_agilo_userstory('My Story', 
                                        rd_points='5', sprint=sprint_name)
        story = teh.load_ticket(t_id=story_id)
        self.assertTrue(story.exists, "Story #%s not created!!!" % story_id)
        self._tester.create_referenced_ticket(story_id, Type.TASK, 
                  'First Task', sprint=sprint_name, remaining_time='8')
        self._tester.create_referenced_ticket(story_id, Type.TASK, 
                  'Second Task', sprint=sprint_name, remaining_time='5')
        self.assertEqual(2, len(story.get_outgoing()))
        second_story_id = self._tester.create_new_agilo_userstory('Another Story', 
                                        rd_points='20', sprint=sprint_name)
        story2 = teh.load_ticket(t_id=second_story_id)
        self.assertTrue(story2.exists, "Story #%s not created!!!" % second_story_id)
        self._tester.create_referenced_ticket(second_story_id, Type.TASK, 
                  'Third Task', sprint=sprint_name, remaining_time='4')
        self.assertEqual(1, len(story2.get_outgoing()))
        # check now the backlog exists and contains all the tickets
        sb = BacklogModelManager(env).get(name="Sprint Backlog", 
                                          scope=sprint_name)
        self.assertEqual(5, sb.count())
        
        self._tester.login_as(Usernames.scrum_master)
        self._tester.navigate_to_sprint_backlog(sprint_name)
        tc.formvalue('backlog_form', 'chk-%d' % story_id, 'on')
        tc.find(button_enabled % 'calculate')
        tc.notfind(button_disabled % 'calculate')
        tc.submit('calculate')
        tc.code(200)
        
        sprint = SprintModelManager(env).get(name=sprint_name)
        self.assertNotEqual(None, sprint)
        metrics = sprint.get_team_metrics()
        self.assertNotEqual(None, metrics)
        
        expected_ratio = float(13.0 / (3 + 2))
        self.assertAlmostEqual(expected_ratio, metrics[Key.RT_USP_RATIO], 3)
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()


class TestCanShowMilestoneBacklog(AgiloTestCase):
    
    def _add_backlog(self, backlog_name, backlogtype):
        url_backlog_admin = self._tester.url + '/admin/agilo/backlogs'
        tc.go(url_backlog_admin)
        
        tc.formvalue('addbacklog', 'name', backlog_name)
        tc.submit('add')
        
        tc.formvalue('modcomp', 'scope', '+%d' % backlogtype)
        tc.formvalue('modcomp', 'ticket_types', '+requirement')
        tc.submit('save')
        tc.code(200)
    
    def assert_action_button_disabled(self, button_name):
        # The idea was to use some code like this for testing:
        #try:
        #    self.tester.select_form_for_twill('backlog_form', button_name)
        #    tc.submit(button_name)
        #    tc.code(200)
        #    self.fail('Button %s should be disabled' % button_name)
        #except:
        #    pass
        # However this does not work due to some bugs in ClientForm/Twill:
        # (see my posting in twill-dev for May 13 2009)
        # Therefore I need to use this very hacky code... :-(
        html = tc.show()
        submit_button_regex = re.compile('(<input type="submit"[^>]+>)', re.IGNORECASE)
        for html_button in submit_button_regex.findall(html):
            if 'name="%s"' % button_name in html_button:
                is_disabled = ('disabled' in html_button)
                self.assertTrue(is_disabled)
    
    def runTest(self):
        milestone_name = 'milestone1'
        self._tester.create_milestone(milestone_name)
        backlog_name = "display_milestone_backlog"
        
        self._tester.login_as(Usernames.admin)
        self._add_backlog(backlog_name, BacklogType.MILESTONE)
        self._tester.navigate_to_milestone_backlog(milestone_name)
        self.assert_action_button_disabled('confirm')
        self.assert_action_button_disabled('calculate')
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()


class TestEditableFieldsInBacklogMustWorkWithUnicodeValues(AgiloTestCase):
    """Test that the backlog can be viewed even if an editable field contains
    non-ascii characters."""
    
    def runTest(self):
        utf8_sprint_name = u'Sprint_MustWorkWithUnicodeValues'.encode('UTF-8')
        utf8_task_summary = u'Eine schöne Zusammenfassung'.encode('UTF-8')
        utf8_owner = u'Łukasz'.encode('UTF-8')
        
        self._tester.login_as(Usernames.admin)
        self._tester.create_sprint_via_admin(utf8_sprint_name, to_datetime(None), duration=10)
        tc.find(utf8_sprint_name)
        self._tester.create_new_agilo_userstory(utf8_task_summary, owner=utf8_owner, sprint=utf8_sprint_name)
        tc.find('<td headers="h_sprint">\s*%s\s*</td>' % utf8_sprint_name, flags='ms')
        self._tester.navigate_to_sprint_backlog(utf8_sprint_name)
        tc.find(utf8_task_summary)
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()


class TestSprintBacklogMustShowAllTicketsForSprintAndReferencedTickets(AgiloTestCase):
    """Check that all necessary tickets are shown in the sprint backlog, 
    especially linked parent tickets. The test flow was modeled according to
    the acceptance criteria for #552"""
    
    def _assert_is_in_sprint_backlog(self, sprint_name, ticket_summary, ticket_id=None):
        self._tester.navigate_to_sprint_backlog(sprint_name)
        tc.find(ticket_summary)
    
    def _assert_is_not_in_sprint_backlog(self, sprint_name, ticket_summary, ticket_id=None):
        self._tester.navigate_to_sprint_backlog(sprint_name)
        tc.notfind(ticket_summary)
    
    def _check_that_unrelated_tickets_dont_show_up(self, sprint_name):
        another_milestone_name = 'AnotherSprintBacklogShowMilestone'
        another_sprint_name = 'AnotherSprintBacklogShow'
        self._tester.create_milestone(another_milestone_name)
        self._tester.create_sprint_for_milestone(another_milestone_name, another_sprint_name, 
                                                 to_datetime(None), duration=10)
        
        self._tester.create_new_agilo_userstory('Story 2', sprint=another_sprint_name)
        self._assert_is_not_in_sprint_backlog(sprint_name, 'Story 2')
        
        self._tester.create_new_agilo_requirement('Requirement 2', milestone=another_milestone_name)
        self._assert_is_not_in_sprint_backlog(sprint_name, 'Requirement 2')
    
    def runTest(self):
        self._tester.login_as(Usernames.admin)
        self._tester.show_type_in_backlog(Key.SPRINT_BACKLOG, 'requirement')
        milestone_name = 'SprintBacklogShowMilestone'
        sprint_name = 'SprintBacklogShow'
        self._tester.create_milestone(milestone_name)
        self._tester.create_sprint_for_milestone(milestone_name, sprint_name, 
                                                 to_datetime(None), duration=10)
        
        story_id = self._tester.create_new_agilo_userstory('Story 1')
        self._tester.create_referenced_ticket(story_id, Type.TASK, 'Task 1')
        self._tester.edit_ticket(story_id, sprint='+%s' % sprint_name)
        
        self._assert_is_in_sprint_backlog(sprint_name, 'Story 1')
        self._assert_is_in_sprint_backlog(sprint_name, 'Task 1')
        
        requirement_id = self._tester.create_new_agilo_requirement('Requirement 1')
        self._tester.link_tickets(requirement_id, story_id)
        self._assert_is_in_sprint_backlog(sprint_name, 'Requirement 1')
        
        self._tester.edit_ticket(requirement_id, milestone='+%s' % milestone_name)
        self._assert_is_in_sprint_backlog(sprint_name, 'Requirement 1')
        
        self._check_that_unrelated_tickets_dont_show_up(sprint_name)
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()


class TestClosedTicketsKeepTheirPositionInBacklog(AgiloTestCase):
    """Check that a ticket keeps its position in the backlog even if it is 
    closed (see bug #675)"""
    
    def _sort_backlog(self):
        tc.formvalue('backlog_form', 'sort', 'click')
        tc.submit('sort')
    
    def runTest(self):
        self._tester.login_as(Usernames.admin)
        milestone_name = 'ClosedTicketsKeepPositionMilestone'
        sprint_name = 'ClosedTicketsKeepPositionSprint'
        self._tester.create_milestone(milestone_name)
        self._tester.create_sprint_for_milestone(milestone_name, sprint_name, 
                                                 to_datetime(None), duration=10)
        
        story_id = self._tester.create_new_agilo_userstory('Story 1', 
                                                           sprint=sprint_name)
        task_id = self._tester.create_referenced_ticket(story_id, Type.TASK, 
                                                'Task 1', sprint=sprint_name)
        self._tester.navigate_to_sprint_backlog(sprint_name)
        self._sort_backlog()
        # We start counting on 0, so position 1 means the task is the 2nd item
        self.assertEquals(1, self._tester.get_position_in_backlog(task_id,
                                                                  Key.SPRINT_BACKLOG,
                                                                  sprint_name))
        self._tester.close_ticket(task_id)
        self._tester.navigate_to_sprint_backlog(sprint_name)
        self.assertEquals(1, self._tester.get_position_in_backlog(task_id,
                                                                  Key.SPRINT_BACKLOG,
                                                                  sprint_name))
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()


class TestBacklogCanBeExportedInCSV(AgiloTestCase):
    
    def _get_exported_product_backlog(self):
        self._tester.navigate_to_product_backlog()
        tc.find('Download in other formats')
        tc.follow('Comma-delimited Text')
        tc.code(200)
        csv_export = tc.show()
        csvfile = CSVFile(StringIO(csv_export), None, 'UTF-8')
        return csvfile
    
    def _find_requirement_info(self, csvfile, req_id):
        req_info = None
        for row in csvfile:
            if int(row.get(Key.ID)) == req_id:
                req_info = row
                break
        self.assertNotEqual(None, req_info)
        return req_info
    
    def runTest(self):
        self._tester.login_as(Usernames.admin)
        req_title = 'Requirement 1'
        properties = {Key.BUSINESS_VALUE: '1200'}
        req_id = self._tester.create_new_agilo_requirement(req_title, **properties)
        
        csvfile = self._get_exported_product_backlog()
        self.assertTrue(Key.SUMMARY in csvfile.get_headings())
        self.assertTrue(Key.DESCRIPTION in csvfile.get_headings())
        self.assertTrue(Key.BUSINESS_VALUE in csvfile.get_headings())
        
        req_info = self._find_requirement_info(csvfile, req_id)
        self.assertEqual(req_title, req_info.get(Key.SUMMARY))
        for key in properties:
            self.assertEqual(properties[key], req_info.get(key))
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()


class TestBacklogContainsSprintEditLink(AgiloTestCase):
    
    def runTest(self):
        self._tester.login_as(Usernames.admin)
        
        milestone_name = 'BacklogContainsSprintEditLinkRelease'
        sprint_name = 'BacklogContainsSprintEditLinkSprint'
        self._tester.create_milestone(milestone_name)
        self._tester.create_sprint_for_milestone(milestone_name, sprint_name,
                                                 to_datetime(None), duration=10)
        
        self._tester.navigate_to_sprint_backlog(sprint_name)
        tc.follow('Edit')
        tc.find('Edit Sprint %s' % sprint_name)
        tc.formvalue('editform', 'end', '')
        tc.formvalue('editform', 'duration', '15')
        tc.submit('save')
        
        # Now we should be redirected to the product backlog again.
        tc.find('Sprint Backlog\s+for\s+%s' % sprint_name)
        
        # Remove Sprints and Milestones from the DB
        self._tester.delete_sprints_and_milestones()



class TestConfirmCommitmentStoresCapacityInMetrics(AgiloTestCase):
    
    def set_default_capacity(self, username):
        member_page = self.tester.go_to_team_member_admin_page(username, self.team_name())
        member_page.set_default_capacity([5] * 5 + [0, 0])
        member_page.submit("save")
    
    def modify_sprint_to_start_and_end_at_midnight(self):
        # We need to get around the proportional capacity calculation
        midnight = datetime.now().replace(hour=6, minute=0, second=0, microsecond=0, tzinfo=localtz)
        end_of_sprint = midnight.replace(hour=23, minute=59) + timedelta(days=13)
        sprint_page = self.tester.go_to_sprint_edit_page(self.sprint_name())
        sprint_page.set_start(midnight)
        sprint_page.set_end(end_of_sprint)
        sprint_page.submit()
    
    def setUp(self, *args, **kwargs):
        super(TestConfirmCommitmentStoresCapacityInMetrics, self).setUp(*args, **kwargs)
        self.tester.login_as(Usernames.admin)
        self.set_sprint_can_start_or_end_on_weekends()
        self.tester.create_sprint_with_small_backlog()
        
        self.modify_sprint_to_start_and_end_at_midnight()
        self.set_default_capacity(self.first_team_member_name())
        self.set_default_capacity(self.second_team_member_name())
    
    def runTest(self):
        self.tester.login_as(Usernames.scrum_master)
        backlog = self.tester.navigate_to_sprint_backlog(self.sprint_name())
        backlog.confirm_commitment()
        
        team_page = self.tester.go_to_team_overview_page(self.team_name())
        self.assertEqual('10.0h', team_page.value_for_sprint(Key.COMMITMENT, self.sprint_name()))
        self.assertEqual('20.0', team_page.value_for_sprint(Key.ESTIMATED_VELOCITY, self.sprint_name()))
        # 10 (working days) * 2 (people) * 5 (capacity per day)
        self.assertEqual('100.0h', team_page.value_for_sprint(Key.CAPACITY, self.sprint_name()))


if __name__ == '__main__':
    from agilo.test.testfinder import run_all_tests
    run_all_tests(__file__)

