##
## test_authentication.py
## Login : <uli@pu.smp.net>
## Started on  Fri Aug 20 08:18:58 2010 Uli Fouquet
## $Id$
## 
## Copyright (C) 2010 Uli Fouquet
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import grok
import unittest
from zope.component import provideAdapter
from zope.component.hooks import setSite
from zope.interface import verify
from zope.pluggableauth.interfaces import  IAuthenticatedPrincipalFactory
from zope.publisher.browser import TestRequest
from zope.publisher.interfaces import IRequest
from zope.session.session import RAMSessionDataContainer
from zope.session.interfaces import ISession
from zope.site import LocalSiteManager
from zope.site.folder import Folder
from zope.site.testing import siteSetUp, siteTearDown
from zope.testing import cleanup
from waeup.sirp.jambtables.authentication import (
    ApplicantsAuthenticatorPlugin, WAeUPApplicantCredentialsPlugin,
    ApplicantCredentials, AuthenticatedApplicantPrincipalFactory,
    ApplicantPrincipalInfo, ApplicantPrincipal)
from waeup.sirp.jambtables.interfaces import(
    IJAMBApplicantSessionCredentials,)

class FakeBatch(dict):
    def getAccessCode(self, id):
        return self.get(id)

class FakeAccessCode(object):
    def __init__(self, repr, inv_date=None):
        self.invalidation_date = inv_date
        self.representation = repr

class FakeInvAccessCode(object):
    invalidation_date = True

class FakeApplication(object):
    def __init__(self, ac=None):
        self.access_code = ac

class FakeSite(grok.Application, grok.Container):
    def __init__(self):
        super(FakeSite, self).__init__()
        self['applications'] = {
            'APP-12345': FakeApplication('APP-12345'),
            'APP-54321': FakeApplication('APP-54321'),
            'APP-22222': FakeApplication('APP-OTHER'),
            'APP-44444': FakeApplication(),
            'APP-55555': FakeApplication('APP-OTHER'),
            'JAMB1': FakeApplication(),
            'JAMB2': FakeApplication('APP-12345'),
            'JAMB3': FakeApplication('APP-54321'),
            'JAMB4': FakeApplication('APP-44444'),
            }
        self['accesscodes'] = {
            'APP': FakeBatch({
                    'APP-12345': FakeAccessCode('APP-12345'),
                    'APP-54321': FakeAccessCode('APP-54321', True),
                    'APP-11111': FakeAccessCode('APP-11111'),
                    'APP-22222': FakeAccessCode('APP-22222'),
                    'APP-33333': FakeAccessCode('APP-33333', True),
                    'APP-44444': FakeAccessCode('APP-44444', True),
                    'APP-55555': FakeAccessCode('APP-55555', True),
                    })
            }

class AuthenticatorPluginTest(unittest.TestCase):

    def setUp(self):
        self.root = Folder()
        siteSetUp(self.root)
        self.app = FakeSite() #grok.Application()
        self.root['app'] = self.app
        self.app.setSiteManager(LocalSiteManager(self.app))
        self.plugin = ApplicantsAuthenticatorPlugin()
        setSite(self.app)
        return

    def tearDown(self):
        siteTearDown()
        return

    def test_invalid_credentials(self):
        result = self.plugin.authenticateCredentials('not-a-dict')
        assert result is None

        result = self.plugin.authenticateCredentials(
            dict(accesscode=None, foo='blah'))
        assert result is None

        result = self.plugin.authenticateCredentials(
            dict(jambregno=None, foo='blah'))
        assert result is None

        result = self.plugin.authenticateCredentials(
            dict(accesscode=None, jambregno=None))
        assert result is None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='Nonsense', jambregno='Nonsense'))
        assert result is None

        # The nine possible cases, where formal correct authentication
        # data is not valid:
        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-22222'))
        assert result is None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-33333'))
        assert result is None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-44444'))
        assert result is None
        
        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-55555'))
        assert result is None
        
        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-12345', jambregno='JAMB-NOT-EXISTENT'))
        assert result is None
        
        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-12345', jambregno='JAMB3'))
        assert result is None
        
        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-54321', jambregno='JAMB-NOT-EXISTENT'))
        assert result is None
        
        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-54321', jambregno='JAMB1'))
        assert result is None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-54321', jambregno='JAMB2'))
        assert result is None
        
        return

    def test_valid_credentials(self):
        """The six different cases where we allow login.

        All other combinations should be forbidden.
        """
        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-11111'))
        assert result is not None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-12345'))
        assert result is not None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-54321'))
        assert result is not None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-12345', jambregno='JAMB1'))

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-12345', jambregno='JAMB2'))
        assert result is not None

        result = self.plugin.authenticateCredentials(
            dict(accesscode='APP-54321', jambregno='JAMB3'))
        assert result is not None

        # check the `principalInfo` method of authenticator
        # plugin. This is only here to satisfy the coverage report.
        assert self.plugin.principalInfo('not-an-id') is None
        return

session_data = {
    'zope.pluggableauth.browserplugins': {}
    }

class FakeSession(dict):
    def __init__(self, request):
        pass

    def get(self, key, default=None):
        return self.__getitem__(key, default)
    
    def __getitem__(self, key, default=None):
        return session_data.get(key, default)

    def __setitem__(self, key, value):
        session_data[key] = value
        return

class CredentialsPluginTest(unittest.TestCase):

    def setUp(self):
        self.request = TestRequest()
        provideAdapter(FakeSession, (IRequest,), ISession)
        self.plugin = WAeUPApplicantCredentialsPlugin()
        self.full_request = TestRequest()
        session_data['zope.pluggableauth.browserplugins'] = {}
        return

    def tearDown(self):
        cleanup.tearDown()
        return

    def filled_request(self, form_dict):
        request = TestRequest()
        for key, value in form_dict.items():
            request.form[key] = value
        return request

    def test_extractCredentials_invalid(self):
        result = self.plugin.extractCredentials('not-a-request')
        assert result is None
        return

    def test_extractCredentials_empty(self):
        result = self.plugin.extractCredentials(self.request)
        assert result is None
        return

    def test_extractCredentials_full_set(self):
        request = self.filled_request({
                'form.prefix': 'APP',
                'form.ac_series': '1',
                'form.ac_number': '1234567890',
                'form.jamb_reg_no': 'JAMB_NUMBER',
                })
        result = self.plugin.extractCredentials(request)
        self.assertEqual(result, {'jambregno': 'JAMB_NUMBER',
                                  'accesscode': 'APP-1-1234567890'})
        return

    def test_extractCredentials_accesscode_only(self):
        request = self.filled_request({
                'form.prefix': 'APP',
                'form.ac_series': '1',
                'form.ac_number': '1234567890',
                })
        result = self.plugin.extractCredentials(request)
        self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
        return

    def test_extractCredentials_from_empty_session(self):
        session_data['zope.pluggableauth.browserplugins']['credentials'] = None
        result = self.plugin.extractCredentials(self.request)
        assert result is None
        return

    def test_extractCredentials_from_nonempty_session(self):
        credentials = ApplicantCredentials('APP-1-12345')
        session_data['zope.pluggableauth.browserplugins'][
            'credentials'] = credentials
        result = self.plugin.extractCredentials(self.request)
        self.assertEqual(result, {'accesscode': 'APP-1-12345'})
        return


class ApplicantCredentialsTest(unittest.TestCase):

    def setUp(self):
        self.credentials = ApplicantCredentials('SOME_ACCESSCODE')
        return

    def tearDown(self):
        return

    def test_methods(self):
        self.assertEqual(self.credentials.getAccessCode(), 'SOME_ACCESSCODE')
        assert self.credentials.getLogin() is None
        assert self.credentials.getPassword() is None
        return

class FakePluggableAuth(object):
    prefix = 'foo'

class PrincipalFactoryTest(unittest.TestCase):

    def setUp(self):
        self.info = ApplicantPrincipalInfo('APP-1-1234567890')
        return

    def tearDown(self):
        pass

    def test_principalFactory_interface(self):
        verify.verifyClass(IAuthenticatedPrincipalFactory,
                           AuthenticatedApplicantPrincipalFactory
                           )
        return

    def test_principalFactory_create(self):
        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)

        assert factory.info is self.info
        assert factory.request is None
        return

    def test_principalFactory_call_w_prefix(self):
        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
        principal = factory(FakePluggableAuth())

        assert isinstance(principal, ApplicantPrincipal)
        self.assertEqual(principal.__repr__(),
                         "ApplicantPrincipal('foo.APP-1-1234567890')")
        self.assertEqual(principal.id, 'foo.APP-1-1234567890')
        return

    def test_principalFactory_call_wo_prefix(self):
        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
        fake_auth = FakePluggableAuth()
        fake_auth.prefix = None
        principal = factory(fake_auth)
        self.assertEqual(principal.id, 'APP-1-1234567890')
        return
        
def test_suite():
    suite = unittest.TestSuite()
    for testcase in [
        AuthenticatorPluginTest, CredentialsPluginTest,
        ApplicantCredentialsTest, PrincipalFactoryTest,
        ]:
        suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
                testcase
                )
        )
    return suite
