# -*- coding: iso-8859-1 -*
##
## <peter@fry-it.com>
##


import unittest
import random

import sys, os
import stat
if __name__ == '__main__':
    execfile(os.path.join(sys.path[0], 'framework.py'))

from Globals import SOFTWARE_HOME    
from Testing import ZopeTestCase
from AccessControl import getSecurityManager
from AccessControl.SecurityManagement import newSecurityManager
from DateTime import DateTime

import Acquisition

ZopeTestCase.installProduct('MailHost')
ZopeTestCase.installProduct('ZCatalog')
ZopeTestCase.installProduct('ZCTextIndex')
ZopeTestCase.installProduct('SiteErrorLog')
ZopeTestCase.installProduct('PythonScripts')
ZopeTestCase.installProduct('IssueTrackerProduct')


#------------------------------------------------------------------------------
#
# Some constants
#

#------------------------------------------------------------------------------

# Open ZODB connection
app = ZopeTestCase.app()
        
# Set up sessioning objects
ZopeTestCase.utils.setupCoreSessions(app)

# Set up the error log
ZopeTestCase.utils.setupSiteErrorLog(app)

# Set up example applications
#if not hasattr(app, 'Examples'):
#    ZopeTestCase.utils.importObjectFromFile(app, examples_path)
        
# Close ZODB connection
ZopeTestCase.close(app)

    

#------------------------------------------------------------------------------

class TestBase(ZopeTestCase.ZopeTestCase):

    def dummy_redirect(self, *a, **kw):
        self.has_redirected = a[0]
        if kw:
            print "*** Redirecting to %r + (%s)" % (a[0], kw)
        else:
            print "*** Redirecting to %r" % a[0]
    
    def afterSetUp(self):
        # install an issue tracker
        dispatcher = self.folder.manage_addProduct['IssueTrackerProduct']
        dispatcher.manage_addIssueTracker('tracker', 'Issue Tracker')
        
        # install an error_log
        #dispatcher = self.folder.manage_addProduct['SiteErrorLog']
        #dispatcher.manage_addErrorLog()
        
        
        # if you set this override you won't be able to do a transaction.get().commit()
        # in the unit tests.
        #self.mexpenses.http_redirect = self.dummy_redirect 
        
        #request = self.app.REQUEST
        #sdm = self.app.session_data_manager
        #request.set('SESSION', sdm.getSessionData())
        
        #self.has_redirected = False

        
from Products.IssueTrackerProduct.Constants import UNICODE_ENCODING, DEBUG
from Globals import DevelopmentMode

class CustomFieldTestCase(TestBase):
    """ 
    """
    
    def _createCustomField(self, id=None, title=None, **kw):
        if id is None:
            id = str(int(10000*random.random()))
        if title is None:
            title = unicode(int(10000*random.random()))
        
        tracker = self.folder.tracker
        adder = tracker.manage_addProduct['IssueTrackerProduct'].manage_addCustomField
            
        return adder(id, title, **kw)
    
    
    def test_creatingCustomField(self):
        """ test to create a custom field account """
        tracker = self.folder.tracker
        
        # if you create a custom field in a tracker with 'create_in_folder'
        # it will make sure it creates the custom field in a 
        # Custom Field Folder object.
        adder = tracker.manage_addProduct['IssueTrackerProduct'].manage_addCustomField
        obj = adder('xxx','title', create_in_folder=True)
        self.assertEqual(obj.getId(), 'xxx')
        self.assertEqual(obj.getTitle(), u'title')
        self.assertTrue(isinstance(obj.getTitle(), unicode))
        self.failUnless(obj.aq_parent.meta_type.endswith('Custom Field Folder'))

        # create another shouldn't create *another* folder
        new_obj = adder('yyy', 'title2', create_in_folder=True)
        self.failUnless(new_obj.aq_parent.absolute_url()== obj.aq_parent.absolute_url())
        
    def test_basic_rendering(self):
        """ basic rendering """
        
        obj = self._createCustomField(id='a', title='A', create_in_folder=True)
        
        rendered = obj.render()
        
        # the rendered HTML should be in unicode
        self.assertTrue(isinstance(rendered, unicode))
        # the __str__ should be ascii if possible
        self.assertTrue(isinstance(str(obj), str))
        
        # expect to find certain things in it
        self.assertTrue(rendered.find('name="a:%s:ustring"'% UNICODE_ENCODING) > -1)
        self.assertTrue(rendered.find('id="id_a"') > -1)
        
    def test_basic_rendering_extra_attributes(self):
        """ basic rendering """
        
        obj = self._createCustomField(id='a', title='A', create_in_folder=True)
        
        rendered = obj.render(id='customid', size=10)
        
        # expect to find certain things in it
        self.assertTrue(rendered.find('id="customid"') > -1)        
        self.assertTrue(rendered.find('size="10"') > -1)
        
        
    def test_basic_rendering_with_extra(self):
        """ basic rendering """
        
        css = 'body{}'
        js = 'alert(document)'
        obj = self._createCustomField(id='a', title='A', create_in_folder=True,
                                      extra_css=css, extra_js=js)
        
        rendered = obj.render()
        
        # in this we should find certain things
        found_css = rendered.find(u'<style type="text/css">\nbody{}\n</style>'); assert found_css > -1
        found_js = rendered.find(u'<script type="text/javascript">\nalert(document)\n</script>'); assert found_js > -1
        found_tag = rendered.find(u'<input id="id_a" name="a:%s:ustring" title="A" />' % UNICODE_ENCODING)
        assert found_tag > -1, rendered
        assert -1 < found_css < found_js < found_tag
        

    def test_render_css(self):
        """ test the <customfield>.render_extra_css() method """
        
        # a path
        obj = self._createCustomField(extra_css='/foo/bar.css')
        rendered = obj.render_extra_css()
        self.assertEqual(rendered, u'<link rel="stylesheet" type="text/css" href="/foo/bar.css" />')
        
        # a payload
        obj = self._createCustomField(extra_css='body { }')
        rendered = obj.render_extra_css()
        self.assertEqual(rendered, u'<style type="text/css">\nbody { }\n</style>')
        
        # nothing
        obj = self._createCustomField()
        rendered = obj.render_extra_css()
        self.assertEqual(rendered, u'')
        
    def test_render_js(self):
        """ test the <customfield>.render_extra_js() method """
        
        # a path
        obj = self._createCustomField(extra_js='foo/bar.js')
        rendered = obj.render_extra_js()
        self.assertEqual(rendered, u'<script type="text/javascript" src="foo/bar.js"></script>')
        
        # a payload
        obj = self._createCustomField(extra_js='function foo() {return;}')
        rendered = obj.render_extra_js()
        self.assertEqual(rendered, u'<script type="text/javascript">\nfunction foo() {return;}\n</script>')
        
        # nothing
        obj = self._createCustomField()
        rendered = obj.render_extra_js()
        self.assertEqual(rendered, u'')
        
    def test_rendering_select(self):
        """ basic rendering """
        
        obj = self._createCustomField(id='a', title='A', 
                                      input_type='select',
                                      options=[('x','X'), ('y','Y')],
                                      )
        
        rendered = obj.render()
        assert rendered.find('id="id_a"') > -1
        assert rendered.find('name="a:%s:ustring"' % UNICODE_ENCODING) > -1
        assert rendered.find('title="A"') > -1
        found_select = rendered.find(u'<select ')
        found_option1 = rendered.find(u'<option value="x">X</option>')
        found_option2 = rendered.find(u'<option value="y">Y</option>')
        found_close = rendered.find(u'</select>')
        assert -1 < found_select < found_option1 < found_option2 < found_close
        
        rendered = obj.render(multiple="multiple")
        found_select = rendered.find(u'<select ')
        found_attr = rendered.find(u'multiple="multiple"')
        found_option1 = rendered.find(u'<option value="x">X</option>')
        found_option2 = rendered.find(u'<option value="y">Y</option>')
        found_close = rendered.find(u'</select>')
        assert -1 < found_select < found_attr < found_option1 < found_option2 < found_close
        
        # what if you want to render it with a particular value
        rendered = obj.render('y')
        found_option1 = rendered.find(u'<option value="x">X</option>')
        found_option2 = rendered.find(u'<option value="y" selected="selected">Y</option>')
        assert -1 < found_option1 < found_option2
        
        # the passed in value can be a list or a tuple
        rendered = obj.render(['y','x'])
        found_option1 = rendered.find(u'<option value="x" selected="selected">X</option>')
        found_option2 = rendered.find(u'<option value="y" selected="selected">Y</option>')
        assert -1 < found_option1 < found_option2
        

    def test_options_expression(self):
        """ if a custom field has a TALES expression on the options_expression
        it needs to be valid for options.
        """
        obj = self._createCustomField(id='a', title='A', create_in_folder=True,
                                      input_type='select'
                                      )
        obj.options_expression = "python:[('a','A'), ('b','B') ]"
        self.assertTrue(obj._valid_options_expression())
        
        obj.options_expression = "python:(u'\xa3', u'\xe4')"
        self.assertTrue(obj._valid_options_expression())
        
        obj.options_expression = "python:['a', ('b','B') ]"
        self.assertTrue(obj._valid_options_expression())
        
        # can't let keys be non-unicode
        obj.options_expression = "python:[('a',nothing), ('b','B') ]"
        self.assertTrue(obj._valid_options_expression())
        
        obj.options_expression = "python:request"
        self.assertFalse(obj._valid_options_expression())
        
        # now, let's try to make it a bit more complicated
        obj.options_expression = "here/objectIds"
        self.assertTrue(obj._valid_options_expression())
        
        obj.options_expression = "here/aq_parent/objectIds"
        self.assertTrue(obj._valid_options_expression())
        
        obj.options_expression = "request/keys"
        self.assertTrue(obj._valid_options_expression())


        # and referring to self
        # this should work since we're logged in with the
        obj.options_expression = "python:[path('object/absolute_url')]"
        self.assertTrue(obj._valid_options_expression())
        
        obj.options = ('x','y','z')
        obj.options_expression = "python:('x','y','z')"
        assert obj._valid_options_expression()
        self.assertEqual(obj.getOptionsIterable(), ['x','y','z'])
        
        
    def test_with_requestform_variables_already_set(self): # crap name!
        """ if certain values are already set in REQUEST.form they
        should be picked up if you render the field.
        """
        obj = self._createCustomField(id='age', title='Age', create_in_folder=True,
                                      input_type='text', python_type='int'
                                      )
        html = obj.render()
        expect = '<input id="id_age" name="age:int" title="Age" />'
        self.assertTrue(html.find(expect) > -1)
        
        self.app.REQUEST.form['age'] = 123
        html = obj.render(self.app.REQUEST)
        expect = '<input id="id_age" name="age:int" title="Age" '\
                 'value="123" />'
        
        # But what if the field as a default value
        #obj = self._createCustomField(id='age', title='Age', create_in_folder=True,
        #                              input_type='text', python_type='int'
        #                              )
        obj.manage_saveFieldProperties(value='0')
        
        html = obj.render()
        expect = '<input id="id_age" name="age:int" title="Age" value="0" />'
        self.assertTrue(html.find(expect) > -1)

        
    def test_basic_input_validation(self):
        """ a custom field can have a default python type, for example 'int'
        which means that we can do a basic validation test. 
        """
        age = self._createCustomField(id='age', title='Age', create_in_folder=True,
                                      python_type='int'
                                      )
        date = self._createCustomField(id='date', title='Date', create_in_folder=True,
                                      python_type='date'
                                      )
        height = self._createCustomField(id='height', title='Height', create_in_folder=True,
                                      python_type='float'
                                      )
                                      
        # the fields have a method called testValidValue() which you 
        # can throw a value at and return a tuple (bool, msg)
        self.assertEqual(age.testValidValue(20), (True, None))
        self.assertEqual(age.testValidValue('20'), (True, None))
        self.assertEqual(age.testValidValue('XX'), (False, u'Not an integer number'))
        
        self.assertEqual(date.testValidValue('2007/01/01'), (True, None))
        self.assertEqual(date.testValidValue('13/12/1979'), (True, None))
        self.assertEqual(date.testValidValue(DateTime()), (True, None))
        self.assertEqual(date.testValidValue('2012/13/13'), (False, u'Not a valid date'))
        
        self.assertEqual(height.testValidValue(20), (True, None))
        self.assertEqual(height.testValidValue(1.3333333333), (True, None))
        self.assertEqual(height.testValidValue('20.3'), (True, None))
        self.assertEqual(height.testValidValue('XX'), (False, u'Not a floating point number'))
        
        
        
    def test_tales_input_validation(self):
        """ a custom field can have a 0 or more validation expressions that
        are rendered by SubmitIssue. If any of them fails you get 
        the validation is considered failed.
        """
        age = self._createCustomField(id='age', title='Age', create_in_folder=True,
                                      python_type='int'
                                      )
        self.assertEqual(age.testValidValue(4), (True, None))
        
        age.manage_addValidationExpression('python:value > 5', "Less or equal to 5")
        self.assertEqual(age.testValidValue(4), (False, u"Less or equal to 5"))
        self.assertEqual(age.testValidValue(6), (True, None))
        
    def test_getCustomFieldObjects(self):
        """ test asking the issuetracker for custom fields """
        def make(id, python_type='ustring', create_in_folder=True):
            return self._createCustomField(id=id, title=id.title(),
                                           create_in_folder=create_in_folder,
                                           python_type=python_type
                                           )
        age = make('age')
        date = make('date')
        height = make('height')
        
        # make one outside
        weight = make('weight', create_in_folder=False)
        
        tracker = self.folder.tracker
        fields = tracker.getCustomFieldObjects()
        
        self.assertEqual(len(fields), 4)
        
        # disable one of them 
        date.disabled = True
        fields = tracker.getCustomFieldObjects()
        self.assertEqual(len(fields), 3)
        
        # set a visibility expression on one of them
        date.disabled = False
        fields = tracker.getCustomFieldObjects()
        assert len(fields) == 4
        
        date.visibility_expression = 'python:0'
        fields = tracker.getCustomFieldObjects()
        self.assertEqual(len(fields), 3)
        # reset
        date.visibility_expression = ''
        
        # filter by ids
        fields = tracker.getCustomFieldObjects(['weight','age'])
        self.assertEqual(len(fields), 2)
        # the order should be as input
        self.assertEqual([x.getId() for x in fields], ['weight','age'])
        
        # filter by ids by a function
        filter_function = lambda x: x.getId() in ('height', 'date')
        fields = tracker.getCustomFieldObjects(filter_function)
        self.assertEqual(len(fields), 2)
        
        # filter by if they should be included in filter options
        filter_function = lambda x: x.includeInFilterOptions()
        fields = tracker.getCustomFieldObjects(filter_function)
        self.assertEqual(len(fields), 0)
        
        age.include_in_filter_options = True
        
        filter_function = lambda x: x.includeInFilterOptions()
        fields = tracker.getCustomFieldObjects(filter_function)
        self.assertEqual(len(fields), 1)
        

    def test_editingMultipleSelect(self):
        """ this test came from running in to a bug and this test
        reassures to not make the mistake again.
        What Titus Brown would call a "stupidity test".
        """
        obj = self._createCustomField(id='locations', title='Locations', 
                                      create_in_folder=True,
                                      python_type='ulines',
                                      input_type='select'
                                      )
        obj.options = [['','Select company first']]
        obj.attributes['multiple'] = 'multiple'
                                      
        html = obj.render()
        expect = u'<select id="id_locations" multiple="multiple" '\
                 'name="locations:utf-8:ulines" title="Locations">\n'\
                 '<option value="">Select company first</option>\n'\
                 '</select>'
                 
        # the output can be padded with debugging stuff
        self.assertTrue(html.find(expect) > -1)
        
        # that was easy, but what could happen is that an AJAX script writes
        # options into select and those are saved.
        
        tracker = self.folder.tracker
        request = self.app.REQUEST
        sdm = self.app.session_data_manager
        request.set('SESSION', sdm.getSessionData())
        
        title = u'titles are working'; request.set('title', title)
        fromname = u'From name'; request.set('fromname', fromname)
        email = u'email@address.com'; request.set('email', email)
        description = u'DESCRIPTION is a in the this test'
        request.set('description', description)
        request.set('type', tracker.getDefaultType())
        request.set('urgency', tracker.getDefaultUrgency())
        
        # important to use forms for later
        request.form['locations'] = [u'foo', u'bar'] 
        tracker.SubmitIssue(request)
        
        assert tracker.getIssueObjects()
        issue = tracker.getIssueObjects()[0]
        self.assertEqual(issue.getCustomFieldData('locations'),
                         [u'foo', u'bar'])
        
        # a fancier way would be to display the value without any form
        fieldsdata = issue.getCustomFieldsData('locations')
        fielddata = fieldsdata[0]
        assert fielddata['field'] == obj, "unable to get field via issue"
        
        value = issue.getCustomFieldData('locations')
        self.assertEqual(obj.showValue(value), u'foo, bar')
        
        # Now if you go to edit the field with a REQUEST which has something
        # in it it should take the REQUEST value before the default
        html = obj.render(request)
        assert 'locations' in request.form.keys()
        self.assertTrue(html.find('<option value="foo" selected="selected">foo</option>')> -1)
        self.assertTrue(html.find('<option value="bar" selected="selected">bar</option>')> -1)
        
        

        
        

        
def test_suite():
    from unittest import TestSuite, makeSuite
    suite = TestSuite()
    suite.addTest(makeSuite(CustomFieldTestCase))
    return suite
    
if __name__ == '__main__':
    framework()
        

