# -*- coding: iso-8859-15 -*-

import httpsession
import re
import sgmllib
import sys
import time
import unittest

glasnostPythonDir = '/usr/local/lib/glasnost-tests'
sys.path.insert(0, glasnostPythonDir)

from GlasnostTestCase import MinimalTestCase, OneUserTestCase

httpsession.PostRequest.follow_redirects = 1

# The dict function is new in Python 2.2.
try:
    dict
except NameError:
    def dict(sequence):
        mapping = {}
        for key, value in sequence:
            mapping[key] = value
        return mapping 

class Form:
    def __init__(self, action = '', method = '', formId = '', **keywords):
        self.actionUrl = action
        self.method = method
        self.id = formId
        self.values = {}
        self.buttons = []
        self.selects = {}

    def set(self, k, v):
        if not self.values.has_key(k):
            raise 'unknown key'
        self.values[k] = v

    def setOption(self, k, v):
        if not k in self.selects.keys():
            raise 'unknown key'
        self.values[k] = self.selects[k][v]

    def getKeywords(self, buttonClicked = None):
        if not buttonClicked:
            buttonClicked = self.buttons[0]
        else:
            buttonClicked = [x for x in self.buttons if x[0] == buttonClicked
                    or x[1] == buttonClicked][0]
        self.values[buttonClicked[0]] = buttonClicked[1]
        return self.values


class ParserForForms(sgmllib.SGMLParser):
    inTextArea = None
    inOption = None

    def __init__(self, session, body):
        sgmllib.SGMLParser.__init__(self)
        self.forms = []
        self.currentForm = None
        self.session = session
        self.feed(body)

    def start_form(self, attrs):
        attrs = dict(attrs) # require python >= 2.2
        self.currentForm = Form(**attrs)
        self.currentForm.session = self.session

    def end_form(self):
        if not self.currentForm:
            raise 'end form while it never opened!'
        self.forms.append(self.currentForm)
        self.currentForm = None

    def start_input(self, attrs):
        attrs = dict(attrs)
        if not self.currentForm:
            return
        if not attrs.has_key('name'):
            attrs['name'] = 'undefined'
        if attrs.has_key('type') and attrs['type'] == 'submit':
            self.currentForm.buttons.append( (attrs['name'], attrs['value']) )
            return
        if attrs.has_key('type') and attrs['type'] == 'checkbox':
            if attrs.has_key('checked') and attrs['checked'] == 'checked':
                checkedValue = 'on'
                if attrs.has_key('value'):
                    checkedValue = '1'
                self.currentForm.values[attrs['name']] = checkedValue
            elif not self.currentForm.values.has_key(attrs['name']):
                self.currentForm.values[attrs['name']] = ''
            return
        self.currentForm.values[attrs['name']] = None
        if attrs.has_key('value'):
            self.currentForm.values[attrs['name']] = attrs['value']
    
    def start_select(self, attrs):
        attrs = dict(attrs)
        if not self.currentForm:
            return
        self.currentForm.values[attrs['name']] = None
        self.inSelect = attrs['name']
        self.currentForm.selects[self.inSelect] = {}

    def end_select(self):
        self.inSelect = None

    def start_textarea(self, attrs):
        attrs = dict(attrs)
        if not self.currentForm:
            return
        if not attrs.has_key('name'):
            return
        self.currentForm.values[attrs['name']] = None
        self.inTextArea = attrs['name']

    def end_textarea(self):
        self.inTextArea = None
        
    def handle_data(self, data):
        if self.inTextArea is not None:
            self.currentForm.values[self.inTextArea] = data
        elif self.inOption is not None:
            select = self.currentForm.selects[self.inSelect]
            current = ''
            if select.has_key(self.inOption):
                current = select[self.inOption]
            select[self.inOption] = current + ' ' + data.strip()

    def start_option(self, attrs):
        if self.inSelect is None:
            return
        attrs = dict(attrs)
        if attrs.has_key('selected') or \
                not self.currentForm.values[self.inSelect]:
            self.currentForm.values[self.inSelect] = attrs['value']
        self.inOption = attrs['value']

    def end_option(self):
        select = self.currentForm.selects[self.inSelect]
        select[self.inOption] = select[self.inOption].strip()
        select[ select[self.inOption] ] = self.inOption
        del select[self.inOption]
        self.inOption = None

class ParserForLinks(sgmllib.SGMLParser):
    def __init__(self, body):
        sgmllib.SGMLParser.__init__(self)
        self.links = []
        self.inLink = 0
        self.feed(body)

    def start_a(self, attrs):
        attrs = dict(attrs) # require python >= 2.2
        if not attrs.has_key('href'):
            return
        self.inLink = 1
        self.links.append(attrs)
        self.links[-1]['data'] = ''

    def handle_data(self, data):
        if not self.inLink:
            return
        self.links[-1]['data'] += data

    def end_a(self):
        try:
            self.links[-1]['data'] = self.links[-1]['data'].strip()
        except IndexError:
            # when first link has not content
            pass
        self.inLink = 0


class WebTestCase(MinimalTestCase, httpsession.HTTPSession):
    pageContent = None
    path = None
    replyCode = None

    links = None
    forms = None

    def setUp(self):
        MinimalTestCase.setUp(self)
        httpsession.HTTPSession.__init__(self, use_cookies = 1)
        self.add_header('User-Agent', 'Glasnost/0.0 (WebTesting)')
        self.add_header('Accept-Language', 'fr')
        self.setHost('localhost:9000')

    def setHost(self, host):
        self.protocol = 'http'
        self.hostName = host
        self.add_header('Host', self.hostName)


    def get(self, path):
        if path.startswith('http'):
            url = path
        else:
            url = '%s://%s%s' % (self.protocol, self.hostName, path)
        url = url.replace('&amp;', '&') # TODO: everything
        req = httpsession.HTTPSession.get(self, url)
        self.handle_req(req)

    def getLinkByLabel(self, label):
        try:
            return [x for x in self.links if x['data'] == label][0]['href']
        except IndexError:
            self.fail('No link with that label')

    def post(self, path, keywords):
        if path.startswith('http'):
            url = path
        else:
            url = '%s://%s%s' % (self.protocol, self.hostName, path)
        url = url.replace('&amp;', '&') # TODO: everything
        req = httpsession.HTTPSession.post(self, url)
        for k, v in keywords.items():
            req.add_param(k, v)
        self.handle_req(req)

    def handle_req(self, req):
        req.getreply()
        self.replyCode = int(req.replycode)
        self.pageContent = req.getfile().read()
        self.links = ParserForLinks(self.pageContent).links
        self.forms = ParserForForms(self, self.pageContent).forms
        self.path = req.path



class BasicTestCase(WebTestCase):
    def test01_getHomepage(self):
        '''Get homepage'''
        self.get('/')
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find('<h1>Home</h1>') > -1)
        self.failUnless(self.pageContent.find('Login') > -1)

    def test02_getVirtualHostsPage(self):
        '''Get virtual hosts page'''
        self.get('/virtualhosts')
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find(
                '<h1>Virtual Hosts</h1>') > -1)

    def test03_getTheVirtualHostPage(self):
        '''Get the virtual host page'''
        self.get('/virtualhosts')
        try:
            link = [x for x in self.links if
                        x['data'] == 'Glasnost' and 
                        re.match('/virtualhosts/\d+', x['href'])] [0]
        except IndexError:
            self.fail('No link to a virtual host object')
        self.get(link['href'])
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find(
                '<h1>Virtual Host - Glasnost</h1>') > -1)
        
    def test04_editTheVirtualHost(self):
        '''Change the language of the virtual host'''
        self.get('/virtualhosts')
        link = [x for x in self.links if
                    x['data'] == 'Glasnost' and 
                    re.match('/virtualhosts/\d+', x['href'])] [0]
        self.get(link['href'])

        self.get( self.getLinkByLabel('Edit') )
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find(
                '<h1>Editing Virtual Host - Glasnost</h1>') > -1)

        try:
            form = [x for x in self.forms if
                        x.actionUrl == '/virtualhosts/submit'][0]
        except IndexError:
            self.fail('No form on this page')

        form.setOption('language', 'French')
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Modify'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find(
                '<h1>Hôte virtuel - Glasnost</h1>') > -1)
        
        self.get( self.getLinkByLabel('Supprimer') )
        self.failUnless(
                re.match('/virtualhosts/\d+/confirmDelete', self.path) )
        form = [x for x in self.forms if x.actionUrl.endswith('delete')][0]
        self.get(form.actionUrl)

    def test05_applyEveryTemplates(self):
        '''Apply every templates'''
        self.get('/virtualhosts')
        link = [x for x in self.links if
                    x['data'] == 'Glasnost' and 
                    re.match('/virtualhosts/\d+', x['href'])] [0]
        self.get(link['href'])
        previousPageContent = self.pageContent

        self.get( self.getLinkByLabel('Edit') )
        form = [x for x in self.forms if
                    x.actionUrl == '/virtualhosts/submit'][0]

        templateNames = form.selects['templateDirectoryName'].keys()
        for templateName in templateNames + ['Glasnost 2']:
            if templateName in ('BxLUG',):
                continue
            self.get(link['href'])
            self.get( self.getLinkByLabel('Edit') )
            form = [x for x in self.forms if
                        x.actionUrl == '/virtualhosts/submit'][0]
            form.setOption('templateDirectoryName', templateName)
            self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Modify'))
            self.failUnless(self.replyCode == 200)
            self.failUnless(self.path == link['href'])
            self.failUnless(self.pageContent.find(
                    '<h1>Virtual Host - Glasnost</h1>') > -1)
            self.failUnless(self.pageContent != previousPageContent)
            previousPageContent = self.pageContent


class AccountManipulationTestCase(WebTestCase):
    def test01_buttonPresence(self):
        '''Look for "New Account" button'''
        self.get('/')
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find('New Account') > -1)
    
    def test02_emptyForm(self):
        '''Submit empty *new account* form'''
        self.get('/')
        self.get( self.getLinkByLabel('New Account') )
        form = [x for x in self.forms if
                    x.actionUrl.endswith('/newAccountSubmit')][0]
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Create'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find('Missing value!') > -1)

    def test03_userButNotUsername(self):
        '''Submit *new account* form filled except the username field'''
        self.get('/')
        self.get( self.getLinkByLabel('New Account') )
        form = [x for x in self.forms if
                    x.actionUrl.endswith('/newAccountSubmit')][0]
        form.setOption('person_language', 'French')
        form.set('person_firstName', 'Test')
        form.set('person_lastName', 'User')
        form.set('person_email', 'test@example.com')
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Create'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find('Missing value!') > -1)

    def test04_userNameButNotUser(self):
        '''Submit *new account* form with just the username field'''
        self.get('/')
        self.get( self.getLinkByLabel('New Account') )
        form = [x for x in self.forms if
                    x.actionUrl.endswith('/newAccountSubmit')][0]
        form.set('account_login', 'test')
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Create'))
        self.failUnless(self.replyCode == 200)

    def test05_fullForm(self):
        '''Submit a correctly filled *new account* form'''
        self.get('/')
        self.get( self.getLinkByLabel('New Account') )
        form = [x for x in self.forms if
                    x.actionUrl.endswith('/newAccountSubmit')][0]
        form.set('account_login', 'test')
        form.setOption('person_language', 'French')
        form.set('person_firstName', 'Test')
        form.set('person_lastName', 'User')
        form.set('person_email', 'test@example.com')
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Create'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.path == '/')
        #self.failUnless(
        #        re.match('/people/\d+$', self.path))

class SelectPasswordTestCase(WebTestCase):
    def test01_setPasswordsToUserEditable(self):
        '''Use admin to tell passwords can be selected by users'''
        self.get('/passwordaccounts/admin')
        self.get( self.getLinkByLabel('Edit') )
        form = [x for x in self.forms if
                    x.actionUrl.endswith('/adminSubmit')][0]
        form.set('userCanChoosePassword', '1')
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Modify'))

    def test02_changePassword(self):
        '''Change the password'''
        self.get('/passwordaccounts')
        self.get( self.getLinkByLabel('test') )
        self.get( self.getLinkByLabel('Edit') )
        form = [x for x in self.forms if
                    x.actionUrl.endswith('/submit')][0]
        form.set('password', 'test-user')
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Modify'))

    def test03_setPasswordBackToGenerated(self):
        '''Use admin to tell passwords are autogenerated'''
        self.get('/passwordaccounts/admin')
        self.get( self.getLinkByLabel('Edit') )
        form = [x for x in self.forms if
                    x.actionUrl.endswith('/adminSubmit')][0]
        del form.values['userCanChoosePassword']
        self.post(form.actionUrl, form.getKeywords(buttonClicked = 'Modify'))

class LoginTestCase(WebTestCase, OneUserTestCase):

    def setUp(self):
        OneUserTestCase.setUp(self)
        WebTestCase.setUp(self)
        self.get('/')
        self.get( self.getLinkByLabel('Login') )
        self.failUnless(self.replyCode == 200)
        self.loginForm = [x for x in self.forms if
                            x.actionUrl.endswith('/loginSubmit')][0]

    def tearDown(self):
        OneUserTestCase.tearDown(self)
        WebTestCase.tearDown(self)
    
    def test01_falseUserName(self):
        '''Login with false username'''
        self.loginForm.set('login', 'blah')
        self.post(self.loginForm.actionUrl,
                self.loginForm.getKeywords(buttonClicked = 'Login'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find('Error!') > -1)

    def test02_realUserNameNoPassword(self):
        '''Login with real username but no password'''
        self.loginForm.set('login', 'test')
        self.post(self.loginForm.actionUrl,
                self.loginForm.getKeywords(buttonClicked = 'Login'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find('Error!') > -1)

    def test03_realUserNameFalsePassword(self):
        '''Login with real username but false password'''
        self.loginForm.set('login', 'test')
        self.loginForm.set('password', 'blah')
        self.post(self.loginForm.actionUrl,
                self.loginForm.getKeywords(buttonClicked = 'Login'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.pageContent.find('Error!') > -1)

    def test04_realUserNameAndPassword(self):
        '''Login with real username and real password'''

        self.loginForm.set('login', 'test')
        self.loginForm.set('password', 'test-user')
        self.post(self.loginForm.actionUrl,
                self.loginForm.getKeywords(buttonClicked = 'Login'))
        self.failUnless(self.replyCode == 200)
        self.failUnless(self.path == '/')
        self.get('/identities/status')
        self.failUnless(self.pageContent.find('You are currently logged') > -1)

        self.get( self.getLinkByLabel('Exit') )
        self.get('/identities/status')
        self.failUnless(self.pageContent.find('You are not currently logged') > -1)

suite1 = unittest.makeSuite(BasicTestCase, 'test')
suite2 = unittest.makeSuite(AccountManipulationTestCase, 'test')
suite3 = unittest.makeSuite(SelectPasswordTestCase, 'test')
suite4 = unittest.makeSuite(LoginTestCase, 'test')

allTests = unittest.TestSuite((suite1, suite2, suite3, suite4))

if __name__ == '__main__':
    unittest.TextTestRunner(verbosity=2).run(allTests)

