class ClientAuthenticationTest(TestCase):

    def inspect_client(self, request, refresh_token=False):
        if not request.client or not request.client.client_id:
            raise ValueError()
        return 'abc'

    def setUp(self):
        self.validator = mock.MagicMock(spec=RequestValidator)
        self.validator.is_pkce_required.return_value = False
        self.validator.get_code_challenge.return_value = None
        self.validator.get_default_redirect_uri.return_value = 'http://i.b./path'
        self.web = WebApplicationServer(self.validator,
                token_generator=self.inspect_client)
        self.mobile = MobileApplicationServer(self.validator,
                token_generator=self.inspect_client)
        self.legacy = LegacyApplicationServer(self.validator,
                token_generator=self.inspect_client)
        self.backend = BackendApplicationServer(self.validator,
                token_generator=self.inspect_client)
        self.token_uri = 'http://example.com/path'
        self.auth_uri = 'http://example.com/path?client_id=abc&response_type=token'
        # should be base64 but no added value in this unittest
        self.basicauth_client_creds = {"Authorization": "john:doe"}
        self.basicauth_client_id = {"Authorization": "john:"}

    def set_client(self, request):
        request.client = mock.MagicMock()
        request.client.client_id = 'mocked'
        return True

    def set_client_id(self, client_id, request):
        request.client = mock.MagicMock()
        request.client.client_id = 'mocked'
        return True

    def basicauth_authenticate_client(self, request):
        assert "Authorization" in request.headers
        assert "john:doe" in request.headers["Authorization"]
        request.client = mock.MagicMock()
        request.client.client_id = 'mocked'
        return True

    def test_client_id_authentication(self):
        token_uri = 'http://example.com/path'

        # authorization code grant
        self.validator.authenticate_client.return_value = False
        self.validator.authenticate_client_id.return_value = False
        _, body, _ = self.web.create_token_response(token_uri,
                body='grant_type=authorization_code&code=mock')
        self.assertEqual(json.loads(body)['error'], 'invalid_client')

        self.validator.authenticate_client_id.return_value = True
        self.validator.authenticate_client.side_effect = self.set_client
        _, body, _ = self.web.create_token_response(token_uri,
                body='grant_type=authorization_code&code=mock')
        self.assertIn('access_token', json.loads(body))

        # implicit grant
        auth_uri = 'http://example.com/path?client_id=abc&response_type=token'
        self.assertRaises(ValueError, self.mobile.create_authorization_response,
                auth_uri, scopes=['random'])

        self.validator.validate_client_id.side_effect = self.set_client_id
        h, _, s = self.mobile.create_authorization_response(auth_uri, scopes=['random'])
        self.assertEqual(302, s)
        self.assertIn('Location', h)
        self.assertIn('access_token', get_fragment_credentials(h['Location']))

    def test_basicauth_web(self):
        self.validator.authenticate_client.side_effect = self.basicauth_authenticate_client
        _, body, _ = self.web.create_token_response(
            self.token_uri,
            body='grant_type=authorization_code&code=mock',
            headers=self.basicauth_client_creds
        )
        self.assertIn('access_token', json.loads(body))

    def test_basicauth_legacy(self):
        self.validator.authenticate_client.side_effect = self.basicauth_authenticate_client
        _, body, _ = self.legacy.create_token_response(
            self.token_uri,
            body='grant_type=password&username=abc&password=secret',
            headers=self.basicauth_client_creds
        )
        self.assertIn('access_token', json.loads(body))

    def test_basicauth_backend(self):
        self.validator.authenticate_client.side_effect = self.basicauth_authenticate_client
        _, body, _ = self.backend.create_token_response(
            self.token_uri,
            body='grant_type=client_credentials',
            headers=self.basicauth_client_creds
        )
        self.assertIn('access_token', json.loads(body))

    def test_basicauth_revoke(self):
        self.validator.authenticate_client.side_effect = self.basicauth_authenticate_client

        # legacy or any other uses the same RevocationEndpoint
        _, body, status = self.legacy.create_revocation_response(
            self.token_uri,
            body='token=foobar',
            headers=self.basicauth_client_creds
        )
        self.assertEqual(status, 200, body)

    def test_basicauth_introspect(self):
        self.validator.authenticate_client.side_effect = self.basicauth_authenticate_client

        # legacy or any other uses the same IntrospectEndpoint
        _, body, status = self.legacy.create_introspect_response(
            self.token_uri,
            body='token=foobar',
            headers=self.basicauth_client_creds
        )
        self.assertEqual(status, 200, body)

    def test_custom_authentication(self):
        token_uri = 'http://example.com/path'

        # authorization code grant
        self.assertRaises(NotImplementedError,
                self.web.create_token_response, token_uri,
                body='grant_type=authorization_code&code=mock')

        # password grant
        self.validator.authenticate_client.return_value = True
        self.assertRaises(NotImplementedError,
                self.legacy.create_token_response, token_uri,
                body='grant_type=password&username=abc&password=secret')

        # client credentials grant
        self.validator.authenticate_client.return_value = True
        self.assertRaises(NotImplementedError,
                self.backend.create_token_response, token_uri,
                body='grant_type=client_credentials')