def _set_up_app(self):
        self._ddb = mock_dynamodb2()
        self._ddb.start()

        from application import configure_logging
        configure_logging()

        setting_overrides = {
            'SQLALCHEMY_DATABASE_URI': 'sqlite://',
            'EQ_DYNAMODB_ENDPOINT': None
        }

        self._application = create_app(setting_overrides)

        self._key_store = KeyStore({
            'keys': {
                EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID: {
                    'purpose':
                    KEY_PURPOSE_AUTHENTICATION,
                    'type':
                    'private',
                    'value':
                    get_file_contents(
                        'third-party/sdc-rrm-authentication-signing-private-v1.pem'
                    )
                },
                SR_USER_AUTHENTICATION_PUBLIC_KEY_KID: {
                    'purpose':
                    KEY_PURPOSE_AUTHENTICATION,
                    'type':
                    'public',
                    'value':
                    get_file_contents(
                        'third-party/sdc-sr-authentication-encryption-public-v1.pem'
                    )
                },
                EQ_SUBMISSION_SDX_PRIVATE_KEY: {
                    'purpose':
                    KEY_PURPOSE_SUBMISSION,
                    'type':
                    'private',
                    'value':
                    get_file_contents(
                        'third-party/sdc-sdx-submission-encryption-private-v1.pem'
                    )
                },
                EQ_SUBMISSION_SR_PRIVATE_SIGNING_KEY: {
                    'purpose':
                    KEY_PURPOSE_SUBMISSION,
                    'type':
                    'public',
                    'value':
                    get_file_contents(
                        'sdc-sr-submission-signing-private-v1.pem')
                },
            }
        })

        self.token_generator = TokenGenerator(
            self._key_store, EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID,
            SR_USER_AUTHENTICATION_PUBLIC_KEY_KID)

        self._client = self._application.test_client()

        with self._application.app_context():
            setup_tables()
Пример #2
0
    def _set_up_app(self):
        self._ds = patch("app.setup.datastore.Client", MockDatastore)
        self._ds.start()

        self._redis = patch("app.setup.redis.Redis", fakeredis.FakeStrictRedis)
        self._redis.start()

        from application import (  # pylint: disable=import-outside-toplevel
            configure_logging, )

        configure_logging()

        setting_overrides = {
            "EQ_ENABLE_HTML_MINIFY": False,
            "EQ_SUBMISSION_CONFIRMATION_BACKEND": "log",
        }

        with patch(
                "google.auth._default._get_explicit_environ_credentials",
                return_value=(Mock(), "test-project-id"),
        ):
            self._application = create_app(setting_overrides)

        self._key_store = KeyStore({
            "keys": {
                EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID: {
                    "purpose":
                    KEY_PURPOSE_AUTHENTICATION,
                    "type":
                    "private",
                    "value":
                    get_file_contents(
                        "sdc-rrm-authentication-signing-private-v1.pem"),
                },
                SR_USER_AUTHENTICATION_PUBLIC_KEY_KID: {
                    "purpose":
                    KEY_PURPOSE_AUTHENTICATION,
                    "type":
                    "public",
                    "value":
                    get_file_contents(
                        "sdc-sr-authentication-encryption-public-v1.pem"),
                },
                EQ_SUBMISSION_SDX_PRIVATE_KEY: {
                    "purpose":
                    KEY_PURPOSE_SUBMISSION,
                    "type":
                    "private",
                    "value":
                    get_file_contents(
                        "sdc-sdx-submission-encryption-private-v1.pem"),
                },
                EQ_SUBMISSION_SR_PRIVATE_SIGNING_KEY: {
                    "purpose":
                    KEY_PURPOSE_SUBMISSION,
                    "type":
                    "public",
                    "value":
                    get_file_contents(
                        "sdc-sr-submission-signing-private-v1.pem"),
                },
            }
        })

        self.token_generator = TokenGenerator(
            self._key_store,
            EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID,
            SR_USER_AUTHENTICATION_PUBLIC_KEY_KID,
        )

        self._client = self._application.test_client()
        self.session = self._client.session_transaction()
class IntegrationTestCase(unittest.TestCase):  # pylint: disable=too-many-public-methods
    def setUp(self):
        # Cache for requests
        self.last_url = None
        self.last_response = None
        self.last_csrf_token = None

        # Perform setup steps
        self._set_up_app()

    def _set_up_app(self):
        self._ddb = mock_dynamodb2()
        self._ddb.start()

        from application import configure_logging
        configure_logging()

        setting_overrides = {
            'SQLALCHEMY_DATABASE_URI': 'sqlite://',
            'EQ_DYNAMODB_ENDPOINT': None
        }

        self._application = create_app(setting_overrides)

        self._key_store = KeyStore({
            'keys': {
                EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID: {
                    'purpose':
                    KEY_PURPOSE_AUTHENTICATION,
                    'type':
                    'private',
                    'value':
                    get_file_contents(
                        'third-party/sdc-rrm-authentication-signing-private-v1.pem'
                    )
                },
                SR_USER_AUTHENTICATION_PUBLIC_KEY_KID: {
                    'purpose':
                    KEY_PURPOSE_AUTHENTICATION,
                    'type':
                    'public',
                    'value':
                    get_file_contents(
                        'third-party/sdc-sr-authentication-encryption-public-v1.pem'
                    )
                },
                EQ_SUBMISSION_SDX_PRIVATE_KEY: {
                    'purpose':
                    KEY_PURPOSE_SUBMISSION,
                    'type':
                    'private',
                    'value':
                    get_file_contents(
                        'third-party/sdc-sdx-submission-encryption-private-v1.pem'
                    )
                },
                EQ_SUBMISSION_SR_PRIVATE_SIGNING_KEY: {
                    'purpose':
                    KEY_PURPOSE_SUBMISSION,
                    'type':
                    'public',
                    'value':
                    get_file_contents(
                        'sdc-sr-submission-signing-private-v1.pem')
                },
            }
        })

        self.token_generator = TokenGenerator(
            self._key_store, EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID,
            SR_USER_AUTHENTICATION_PUBLIC_KEY_KID)

        self._client = self._application.test_client()

        with self._application.app_context():
            setup_tables()

    def tearDown(self):
        self._ddb.stop()

        flask_theme_cache.clear()
        schema_utils.clear_schema_cache()

    def launchSurvey(self,
                     eq_id='test',
                     form_type_id='dates',
                     **payload_kwargs):
        """
        Launch a survey as an authenticated user and follow re-directs
        :param eq_id: The id of the survey to launch e.g. 'census', 'test' etc.
        :param form_type_id: The form type of the survey e.g. 'household', 'radio' etc.
        """
        token = self.token_generator.create_token(form_type_id=form_type_id,
                                                  eq_id=eq_id,
                                                  **payload_kwargs)
        self.get('/session?token=' + token)

    def dumpAnswers(self):

        self.get('/dump/answers')

        # Then I get a 200 OK response
        self.assertStatusOK()

        # And the JSON response contains the data I submitted
        dump_answers = json.loads(self.getResponseData())
        return dump_answers

    def dumpSubmission(self):

        self.get('/dump/submission')

        # Then I get a 200 OK response
        self.assertStatusOK()

        # And the JSON response contains the data I submitted
        dump_submission = json.loads(self.getResponseData())
        return dump_submission

    def get(self, url, **kwargs):
        """
        GETs the specified URL, following any redirects.

        If the response contains a CSRF token; it is cached to be use on
        the next POST.

        The URL will be cached for future POST requests.

        :param url: the URL to GET
        """
        environ, response = self._client.get(url,
                                             as_tuple=True,
                                             follow_redirects=True,
                                             **kwargs)

        self._cache_response(environ, response)

    def post(self,
             post_data=None,
             url=None,
             action='save_continue',
             action_value='',
             **kwargs):
        """
        POSTs to the specified URL with post_data and performs a GET
        with the URL from the re-direct.

        Will add the last received CSRF token to the post_data automatically.

        :param url: the URL to POST to; use None to use the last received URL
        :param post_data: the data to POST
        :param action: The button action to post
        """
        if url is None:
            url = self.last_url

        self.assertIsNotNone(url)

        _post_data = (post_data.copy() or {}) if post_data else {}
        if self.last_csrf_token is not None:
            _post_data.update({'csrf_token': self.last_csrf_token})

        if action:
            _post_data.update(
                {'action[{action}]'.format(action=action): action_value})

        environ, response = self._client.post(url,
                                              data=_post_data,
                                              as_tuple=True,
                                              follow_redirects=True,
                                              **kwargs)

        self._cache_response(environ, response)

    def _cache_response(self, environ, response):
        self.last_csrf_token = self._extract_csrf_token(
            response.get_data(True))
        self.last_response = response
        self.last_url = environ['PATH_INFO']
        if environ['QUERY_STRING']:
            self.last_url += '?' + environ['QUERY_STRING']

    @staticmethod
    def _extract_csrf_token(html):
        match = re.search(
            r'<input id="csrf_token" name="csrf_token" type="hidden" value="(.+?)">',
            html)
        return (match.group(1) or None) if match else None

    def getResponseData(self):
        """
        Returns the last received response data
        """
        return self.last_response.get_data(True)

    def getHtmlSoup(self):
        """
        Returns the last received response data as a BeautifulSoup HTML object
        See https://www.crummy.com/software/BeautifulSoup/bs4/doc/
        :return: a BeautifulSoup object for the response data
        """
        return BeautifulSoup(self.getResponseData(), 'html.parser')

    # Extra Helper Assertions
    def assertInPage(self, content, message=None):
        self.assertIn(member=str(content),
                      container=self.getResponseData(),
                      msg=str(message))

    def assertInSelector(self, content, **selectors):
        data = self.getHtmlSoup().find(**selectors)
        message = '\n{} not in \n{}'.format(content, data)

        # intentionally not using assertIn to avoid duplicating the output message
        self.assertTrue(content in str(data), msg=message)

    def assertNotInPage(self, content, message=None):

        self.assertNotIn(member=str(content),
                         container=self.getResponseData(),
                         msg=str(message))

    def assertRegexPage(self, regex, message=None):

        self.assertRegex(text=self.getResponseData(),
                         expected_regex=str(regex),
                         msg=str(message))

    def assertEqualPageTitle(self, title):
        self.assertEqual(self.getHtmlSoup().title.string, title)  # pylint: disable=no-member

    def assertStatusOK(self):
        self.assertStatusCode(200)

    def assertStatusUnauthorised(self):
        self.assertStatusCode(401)

    def assertStatusForbidden(self):
        self.assertStatusCode(403)

    def assertStatusNotFound(self):
        self.assertStatusCode(404)

    def assertStatusCode(self, status_code):
        if self.last_response is not None:
            self.assertEqual(self.last_response.status_code, status_code)
        else:
            self.fail('last_response is invalid')

    def assertEqualUrl(self, url):
        if self.last_url:
            self.assertEqual(url, self.last_url)
        else:
            self.fail('last_url is invalid')

    def assertInUrl(self, content):
        if self.last_url:
            self.assertIn(content, self.last_url)
        else:
            self.fail('last_url is invalid')

    def assertNotInUrl(self, content):
        if self.last_url:
            self.assertNotIn(content, self.last_url)
        else:
            self.fail('last_url is invalid')

    def assertRegexUrl(self, regex):
        if self.last_url:
            self.assertRegex(text=self.last_url, expected_regex=regex)
        else:
            self.fail('last_url is invalid')
Пример #4
0
class IntegrationTestCase(unittest.TestCase):  # pylint: disable=too-many-public-methods
    def setUp(self):
        # Cache for requests
        self.last_url = None
        self.last_response = None
        self.last_csrf_token = None
        self.redirect_url = None

        # Perform setup steps
        self._set_up_app()

    @property
    def test_app(self):
        return self._application

    def _set_up_app(self):
        self._ds = patch("app.setup.datastore.Client", MockDatastore)
        self._ds.start()

        self._redis = patch("app.setup.redis.Redis", fakeredis.FakeStrictRedis)
        self._redis.start()

        from application import (  # pylint: disable=import-outside-toplevel
            configure_logging, )

        configure_logging()

        setting_overrides = {
            "EQ_ENABLE_HTML_MINIFY": False,
            "EQ_SUBMISSION_CONFIRMATION_BACKEND": "log",
        }

        with patch(
                "google.auth._default._get_explicit_environ_credentials",
                return_value=(Mock(), "test-project-id"),
        ):
            self._application = create_app(setting_overrides)

        self._key_store = KeyStore({
            "keys": {
                EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID: {
                    "purpose":
                    KEY_PURPOSE_AUTHENTICATION,
                    "type":
                    "private",
                    "value":
                    get_file_contents(
                        "sdc-rrm-authentication-signing-private-v1.pem"),
                },
                SR_USER_AUTHENTICATION_PUBLIC_KEY_KID: {
                    "purpose":
                    KEY_PURPOSE_AUTHENTICATION,
                    "type":
                    "public",
                    "value":
                    get_file_contents(
                        "sdc-sr-authentication-encryption-public-v1.pem"),
                },
                EQ_SUBMISSION_SDX_PRIVATE_KEY: {
                    "purpose":
                    KEY_PURPOSE_SUBMISSION,
                    "type":
                    "private",
                    "value":
                    get_file_contents(
                        "sdc-sdx-submission-encryption-private-v1.pem"),
                },
                EQ_SUBMISSION_SR_PRIVATE_SIGNING_KEY: {
                    "purpose":
                    KEY_PURPOSE_SUBMISSION,
                    "type":
                    "public",
                    "value":
                    get_file_contents(
                        "sdc-sr-submission-signing-private-v1.pem"),
                },
            }
        })

        self.token_generator = TokenGenerator(
            self._key_store,
            EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID,
            SR_USER_AUTHENTICATION_PUBLIC_KEY_KID,
        )

        self._client = self._application.test_client()
        self.session = self._client.session_transaction()

    def tearDown(self):
        self._ds.stop()
        self._redis.stop()

    def launchSurvey(self, schema_name="test_dates", **payload_kwargs):
        """
        Launch a survey as an authenticated user and follow re-directs
        :param schema_name: The name of the schema to load
        """
        token = self.token_generator.create_token(schema_name=schema_name,
                                                  **payload_kwargs)
        self.get("/session?token=" + token)

    def dumpAnswers(self):

        self.get("/dump/answers")

        # Then I get a 200 OK response
        self.assertStatusOK()

        # And the JSON response contains the data I submitted
        dump_answers = json.loads(self.getResponseData())
        return dump_answers

    def dumpSubmission(self):

        self.get("/dump/submission")

        # Then I get a 200 OK response
        self.assertStatusOK()

        # And the JSON response contains the data I submitted
        dump_submission = json.loads(self.getResponseData())
        return dump_submission

    def dump_debug(self):
        self.get("/dump/debug")
        self.assertStatusOK()
        return json.loads(self.getResponseData())

    def get(self, url, follow_redirects=True, **kwargs):
        """
        GETs the specified URL, following any redirects.

        If the response contains a CSRF token; it is cached to be use on
        the next POST.

        The URL will be cached for future POST requests.

        :param url: the URL to GET
        """
        environ, response = self._client.get(url,
                                             as_tuple=True,
                                             follow_redirects=follow_redirects,
                                             **kwargs)

        self._cache_response(environ, response)

    def post(self, post_data=None, url=None, action=None, **kwargs):
        """
        POSTs to the specified URL with post_data and performs a GET
        with the URL from the re-direct.

        Will add the last received CSRF token to the post_data automatically.

        :param url: the URL to POST to; use None to use the last received URL
        :param post_data: the data to POST
        :param action: The button action to post
        """
        if url is None:
            url = self.last_url

        self.assertIsNotNone(url)

        _post_data = (post_data.copy() or {}) if post_data else {}
        if self.last_csrf_token is not None:
            _post_data.update({"csrf_token": self.last_csrf_token})

        if action:
            _post_data.update({f"action[{action}]": ""})

        environ, response = self._client.post(url,
                                              data=_post_data,
                                              as_tuple=True,
                                              follow_redirects=True,
                                              **kwargs)

        self._cache_response(environ, response)

    def head(self, url, **kwargs):
        """
        Send a HEAD request to the specified URL.

        :param url: the URL to send a HEAD request to
        """
        environ, response = self._client.head(url, as_tuple=True, **kwargs)

        self._cache_response(environ, response)

    def options(self, url, **kwargs):
        """
        Send an OPTIONS request to the specified URL.

        :param url: the URL to send an OPTION request to
        """
        environ, response = self._client.options(url, as_tuple=True, **kwargs)

        self._cache_response(environ, response)

    def sign_out(self):
        selected = self.getHtmlSoup().find("a", {"name": "btn-save-sign-out"})
        return self.get(selected["href"])

    def exit(self):
        """
        GETs the sign-out url from the exit button. Does not follow the external
        redirect.
        """
        url = self.getHtmlSoup().find("a", {"name": "btn-exit"})["href"]
        self.get(url, follow_redirects=False)

    def previous(self):
        selector = "#top-previous"
        selected = self.getHtmlSoup().select(selector)
        return self.get(selected[0].get("href"))

    def _cache_response(self, environ, response):
        self.last_csrf_token = self._extract_csrf_token(
            response.get_data(True))
        self.redirect_url = response.headers.get("Location")
        self.last_response = response
        self.last_url = environ["PATH_INFO"]
        if environ["QUERY_STRING"]:
            self.last_url += "?" + environ["QUERY_STRING"]

    @staticmethod
    def _extract_csrf_token(html):
        match = re.search(
            r'<input id="csrf_token" name="csrf_token" type="hidden" value="(.+?)"/>',
            html,
        )
        return (match.group(1) or None) if match else None

    def getResponseData(self):
        """
        Returns the last received response data
        """
        return self.last_response.get_data(True)

    def getCookie(self):
        """
        Returns the last received response cookie session
        """
        cookie = self.last_response.headers["Set-Cookie"]
        cookie_session = cookie.split("session=.")[1].split(";")[0]
        decoded_cookie_session = decode_flask_cookie(cookie_session)
        return json.loads(decoded_cookie_session)

    def getHtmlSoup(self):
        """
        Returns the last received response data as a BeautifulSoup HTML object
        See https://www.crummy.com/software/BeautifulSoup/bs4/doc/
        :return: a BeautifulSoup object for the response data
        """
        return BeautifulSoup(self.getResponseData(), "html.parser")

    # Extra Helper Assertions
    def assertInHead(self, content):
        self.assertInSelector(content, "head")

    # Extra Helper Assertions
    def assertInBody(self, content):
        self.assertInSelector(content, "body")

    # Extra Helper Assertions
    def assertNotInHead(self, content):
        self.assertNotInSelector(content, "head")

    # Extra Helper Assertions
    def assertNotInBody(self, content):
        self.assertNotInSelector(content, "body")

    def assertInSelector(self, content, selector):
        data = self.getHtmlSoup().select(selector)
        message = "\n{} not in \n{}".format(content, data)

        # intentionally not using assertIn to avoid duplicating the output message
        self.assertTrue(content in str(data), msg=message)

    def assertInSelectorCSS(self, content, *selectors, **kwargs):
        data = self.getHtmlSoup().find(*selectors, **kwargs)
        message = "\n{} not in \n{}".format(content, data)

        # intentionally not using assertIn to avoid duplicating the output message
        self.assertTrue(content in str(data), msg=message)

    def assertNotInSelector(self, content, selector):
        data = self.getHtmlSoup().select(selector)
        message = "\n{} in \n{}".format(content, data)

        # intentionally not using assertIn to avoid duplicating the output message
        self.assertFalse(content in str(data), msg=message)

    def assertNotInPage(self, content, message=None):

        self.assertNotIn(member=str(content),
                         container=self.getResponseData(),
                         msg=str(message))

    def assertRegexPage(self, regex, message=None):

        self.assertRegex(text=self.getResponseData(),
                         expected_regex=str(regex),
                         msg=str(message))

    def assertEqualPageTitle(self, title):
        self.assertEqual(title, self.getHtmlSoup().title.string)

    def assertStatusOK(self):
        self.assertStatusCode(200)

    def assertBadRequest(self):
        self.assertStatusCode(400)

    def assertStatusUnauthorised(self):
        self.assertStatusCode(401)

    def assertStatusForbidden(self):
        self.assertStatusCode(403)

    def assertStatusNotFound(self):
        self.assertStatusCode(404)
        self.assertInBody("Page not found")

    def assertStatusCode(self, status_code):
        if self.last_response is not None:
            self.assertEqual(self.last_response.status_code, status_code)
        else:
            self.fail("last_response is invalid")

    def assertEqualUrl(self, url):
        if self.last_url:
            self.assertEqual(url, self.last_url)
        else:
            self.fail("last_url is invalid")

    def assertInUrl(self, content):
        if self.last_url:
            self.assertIn(content, self.last_url)
        else:
            self.fail("last_url is invalid")

    def assertNotInUrl(self, content):
        if self.last_url:
            self.assertNotIn(content, self.last_url)
        else:
            self.fail("last_url is invalid")

    def assertRegexUrl(self, regex):
        if self.last_url:
            self.assertRegex(text=self.last_url, expected_regex=regex)
        else:
            self.fail("last_url is invalid")

    def assertInRedirect(self, content):
        if self.redirect_url:
            self.assertIn(content, self.redirect_url)
        else:
            self.fail("no redirect found")