def get_framework_contract_title(frameworks_path: str, framework_slug: str) -> str: """The contract title is different for G-Cloud and DOS. Look up the correct name with the content loader""" content_loader = ContentLoader(frameworks_path) content_loader.load_messages(framework_slug, ["e-signature"]) return str( content_loader.get_message(framework_slug, "e-signature", "framework_contract_title"))
def generate_schema(framework_slug, question_set, manifest_name): loader = ContentLoader("./") loader.load_manifest( framework_slug, question_set, manifest_name, ) manifest = loader.get_manifest( framework_slug, manifest_name, ) assessed_questions = tuple(question for question in chain.from_iterable( section.questions for section in manifest.sections) if "passIfIn" in question.get("assessment", {})) discretionary_properties = { question.id: _enum_for_question(question) for question in assessed_questions if question["assessment"].get("discretionary") } baseline_properties = { question.id: _enum_for_question(question) for question in assessed_questions if not question["assessment"].get("discretionary") } return { "$schema": "http://json-schema.org/draft-07/schema#", # hardcoded to draft 7 because jsonschema > 3 supports it "title": "{} Declaration Assessment Schema (Definite Pass Schema)".format( framework_slug), "type": "object", "allOf": [ { "$ref": "#/definitions/baseline" }, { "properties": discretionary_properties }, ], "definitions": { "baseline": { "$schema": "http://json-schema.org/draft-04/schema#", "title": "{} Declaration Assessment Schema (Baseline Schema)".format( framework_slug), "type": "object", "properties": baseline_properties, }, }, }
def _get_questions_by_type(framework_slug, doc_type, question_types): manifest_name = '{}_search_filters'.format(doc_type) loader = ContentLoader(_base_dir) loader.load_manifest( framework_slug, doc_type, manifest_name, ) manifest = loader.get_manifest(framework_slug, manifest_name) return (q for q in sum((s.questions for s in manifest.sections), []) if q.type in question_types)
def load_questions(schema_type, framework_slug, lot_slug): loader = ContentLoader('./') loader.load_manifest(framework_slug, MANIFESTS[schema_type]['question_set'], MANIFESTS[schema_type]['manifest']) manifest = loader.get_manifest(framework_slug, MANIFESTS[schema_type]['manifest']).filter( {'lot': lot_slug}, dynamic=False) return { q['id']: q for q in sum((s.questions for s in manifest.sections), []) }
def load_questions(schema_type, framework_slug, lot_slug): loader = ContentLoader('./') loader.load_manifest( framework_slug, MANIFESTS[schema_type]['question_set'], MANIFESTS[schema_type]['manifest'] ) manifest = loader.get_manifest(framework_slug, MANIFESTS[schema_type]['manifest']).filter( {'lot': lot_slug}, dynamic=False ) return {q['id']: q for q in sum((s.questions for s in manifest.sections), [])}
def generate_schema(framework_slug, question_set, manifest_name): loader = ContentLoader("./") loader.load_manifest( framework_slug, question_set, manifest_name, ) manifest = loader.get_manifest( framework_slug, manifest_name, ) assessed_questions = tuple( question for question in chain.from_iterable(section.questions for section in manifest.sections) if "passIfIn" in question.get("assessment", {}) ) discretionary_properties = { question.id: _enum_for_question(question) for question in assessed_questions if question["assessment"].get("discretionary") } baseline_properties = { question.id: _enum_for_question(question) for question in assessed_questions if not question["assessment"].get("discretionary") } return { "$schema": "http://json-schema.org/draft-07/schema#", # hardcoded to draft 7 because jsonschema > 3 supports it "title": "{} Declaration Assessment Schema (Definite Pass Schema)".format(framework_slug), "type": "object", "allOf": [ {"$ref": "#/definitions/baseline"}, {"properties": discretionary_properties}, ], "definitions": { "baseline": { "$schema": "http://json-schema.org/draft-04/schema#", "title": "{} Declaration Assessment Schema (Baseline Schema)".format(framework_slug), "type": "object", "properties": baseline_properties, }, }, }
import glob import mock import pytest from dmcontent import ContentLoader from dmcontent.utils import TemplateField content = ContentLoader('./') MANIFEST_QUESTION_SET = { "display_brief": "briefs", "edit_brief": "briefs", "display_brief_response": "brief-responses", "legacy_display_brief_response": "brief-responses", "edit_brief_response": "brief-responses", "legacy_edit_brief_response": "brief-responses", "output_brief_response": "brief-responses", "legacy_output_brief_response": "brief-responses", "clarification_question": "clarification_question", "declaration": "declaration", "display_service": "services", "edit_service": "services", "edit_service_as_admin": "services", "edit_submission": "services", "search_filters": "services", } # `item` property wouldn't usually be part of a brief response but is included here as the dynamic list child question # `evidence` relies on an `item` in its context to render QUESTION_SET_CONTEXT = {
def generate_schema(framework_slug, question_set, manifest_name): loader = ContentLoader(_base_dir) loader.load_manifest( framework_slug, question_set, manifest_name, ) manifest = loader.get_manifest( framework_slug, manifest_name, ) try: with open( os.path.join( _base_dir, "frameworks", framework_slug, "assessment", manifest_name, "extra.schema.json", ), "r") as f: extra_schema = json.load(f) except FileNotFoundError: extra_schema = {} assessed_questions = [] for question in chain.from_iterable(section.questions for section in manifest.sections): if question.type == 'multiquestion': for nested_question in question.questions: if "passIfIn" in nested_question.get("assessment", {}): assessed_questions.append(nested_question) elif "passIfIn" in question.get("assessment", {}): assessed_questions.append(question) discretionary_questions = tuple(q for q in assessed_questions if q["assessment"].get("discretionary")) baseline_questions = tuple(q for q in assessed_questions if not q["assessment"].get("discretionary")) # we add all assessed_questions as "required" by default, not because it's the assessment schema's job to # enforce that requirement, but it's a stronger guarantee we keep the content, the tests for this function # and the custom validation in the supplier frontend in agreement with each other. generated_schema = { "$schema": "http://json-schema.org/draft-07/schema#", # hardcoded to draft 7 because jsonschema > 3 supports it "title": "{} Declaration Assessment Schema (Definite Pass Schema)".format( framework_slug), "type": "object", "allOf": [ { "$ref": "#/definitions/baseline" }, { "properties": {q.id: _enum_for_question(q) for q in discretionary_questions} }, ], "required": sorted(q.id for q in discretionary_questions if q["assessment"].get("required", True)), "definitions": { "baseline": { "$schema": "http://json-schema.org/draft-04/schema#", "title": "{} Declaration Assessment Schema (Baseline Schema)".format( framework_slug), "type": "object", "allOf": [ { "properties": { q.id: _enum_for_question(q) for q in baseline_questions } }, ], "required": sorted(q.id for q in baseline_questions if q["assessment"].get("required", True)), }, }, } return always_merger.merge(generated_schema, extra_schema)
class BaseApplicationTest(object): injected_content_loader = ContentLoader('app/content') def setup_method(self, method): # We need to mock the API client in create_app, however we can't use patch the constructor, # as the DataAPIClient instance has already been created; nor can we temporarily replace app.data_api_client # with a mock, because then the shared instance won't have been configured (done in create_app). Instead, # just mock the one function that would make an API call in this case. data_api_client.find_frameworks = mock.Mock() # the value set here has an effect on the content that gets initially loaded by the content loader by default data_api_client.find_frameworks.return_value = self._get_frameworks_list_fixture_data() # if we don't make this tweak, the content loader will get re-built for every test, which is incredibly slow. # instead we replace the `_make_content_loader_factory` with a variant which injects `injected_content_loader` # as the `initial_instance` argument, which we keep as a class attribute. `_make_content_loader_factory` still # executes inside `create_app`, but all the content it asks to be loaded should already be present in the # content_loader it is operating on, so it effectively does nothing. # a test that needed a "clean" content loader for some reason would be able to override a test instance's # injected_content_loader early in the setup_method process (e.g. with None) self.make_content_loader_factory_mock = mock.patch("app._make_content_loader_factory") self.make_content_loader_factory_mock.start().side_effect = partial( _make_content_loader_factory, initial_instance=self.injected_content_loader, ) self.app_env_var_mock = mock.patch.dict('gds_metrics.os.environ', {'PROMETHEUS_METRICS_PATH': '/_metrics'}) self.app_env_var_mock.start() self.session_mock = mock.patch('dmutils.session.init_app') self.session_mock.start() self.app = create_app('test') self.client = self.app.test_client() self._s3_patch = mock.patch('dmutils.s3.S3') self.s3 = self._s3_patch.start() self.s3.return_value = mock.Mock() self.s3.return_value.list.return_value = [] self._default_suffix_patch = mock.patch( 'dmutils.documents.default_file_suffix', return_value='2015-01-01-1200' ) self._default_suffix_patch.start() def teardown_method(self, method): self._s3_patch.stop() self._default_suffix_patch.stop() self.app_env_var_mock.stop() self.session_mock.stop() self.make_content_loader_factory_mock.stop() def load_example_listing(self, name): file_path = os.path.join("example_responses", "{}.json".format(name)) with open(file_path) as f: return json.load(f) @staticmethod def strip_all_whitespace(content): pattern = re.compile(r'\s+') return re.sub(pattern, '', content) def assert_flashes(self, expected_message, expected_category='message'): with self.client.session_transaction() as session: if '_flashes' not in session: raise AssertionError('nothing flashed') messages = MultiDict(session['_flashes']) assert expected_message in messages.getlist(expected_category), \ "Didn't find '{}' in '{}'".format(expected_message, messages.getlist(expected_category)) def assert_no_flashes(self): with self.client.session_transaction() as session: assert '_flashes' not in session @staticmethod def _get_fixture_data(fixture_filename): test_root = os.path.abspath( os.path.join(os.path.dirname(__file__), ".") ) fixture_path = os.path.join( test_root, 'fixtures', fixture_filename ) with open(fixture_path) as fixture_file: return json.load(fixture_file) @staticmethod def _get_frameworks_list_fixture_data(): return BaseApplicationTest._get_fixture_data('frameworks.json')
class BaseApplicationTest(object): injected_content_loader = ContentLoader('app/content') def setup_method(self, method): """ A data_api_client instance is required for `create_app`, so we need some careful patching to initialise the Flask test client: - patch the .find_frameworks() method of `app.data_api_client` with the fixture - initialise the app with `create_app('test')` - in the tests, use a subclass of `BaseAPIClientMixin` above, with the path to the imported data_api_client - the .find_frameworks() return value will need to be provided separately in those tests, as the import path will (hopefully!) be different there. """ self.app_env_var_mock = mock.patch.dict( 'gds_metrics.os.environ', {'PROMETHEUS_METRICS_PATH': '/_metrics'}) self.app_env_var_mock.start() self.session_mock = mock.patch('dmutils.session.init_app') self.session_mock.start() data_api_client.find_frameworks = mock.Mock() data_api_client.find_frameworks.return_value = self._get_frameworks_list_fixture_data( ) # if we don't make this tweak, the content loader will get re-built for every test, which is incredibly slow. # instead we replace the `_make_content_loader_factory` with a variant which injects `injected_content_loader` # as the `initial_instance` argument, which we keep as a class attribute. `_make_content_loader_factory` still # executes inside `create_app`, but all the content it asks to be loaded should already be present in the # content_loader it is operating on, so it effectively does nothing. # a test that needed a "clean" content loader for some reason would be able to override a test instance's # injected_content_loader early in the setup_method process (e.g. with None) self.make_content_loader_factory_mock = mock.patch( "app._make_content_loader_factory") self.make_content_loader_factory_mock.start().side_effect = partial( _make_content_loader_factory, initial_instance=self.injected_content_loader, ) self.app = create_app('test') self.app.register_blueprint(login_for_tests) self.client = self.app.test_client() self.get_user_patch = None def teardown_method(self, method): self.teardown_login() self.make_content_loader_factory_mock.stop() self.app_env_var_mock.stop() self.session_mock.stop() @staticmethod def user(id, email_address, supplier_id, supplier_name, name, is_token_valid=True, locked=False, active=True, role='buyer'): hours_offset = -1 if is_token_valid else 1 date = datetime.utcnow() + timedelta(hours=hours_offset) password_changed_at = date.strftime(DATETIME_FORMAT) user = { "id": id, "emailAddress": email_address, "name": name, "role": role, "locked": locked, 'active': active, 'passwordChangedAt': password_changed_at } if supplier_id: supplier = { "supplierId": supplier_id, "name": supplier_name, } user['role'] = 'supplier' user['supplier'] = supplier return {"users": user} @staticmethod def _get_fixture_data(fixture_filename): test_root = os.path.abspath( os.path.join(os.path.dirname(__file__), ".")) fixture_path = os.path.join(test_root, 'fixtures', fixture_filename) with open(fixture_path) as fixture_file: return json.load(fixture_file) @staticmethod def _get_search_results_fixture_data(): return BaseApplicationTest._get_fixture_data( 'search_results_fixture.json') @staticmethod def _get_g9_search_results_fixture_data(): return BaseApplicationTest._get_fixture_data( 'g9_search_results_fixture.json') @staticmethod def _get_search_results_multiple_page_fixture_data(): return BaseApplicationTest._get_fixture_data( 'search_results_multiple_pages_fixture.json') @staticmethod def _get_frameworks_list_fixture_data(): return get_frameworks_list_fixture_data() @staticmethod def _get_g4_service_fixture_data(): return BaseApplicationTest._get_fixture_data('g4_service_fixture.json') @staticmethod def _get_g5_service_fixture_data(): return BaseApplicationTest._get_fixture_data('g5_service_fixture.json') @staticmethod def _get_g6_service_fixture_data(): return BaseApplicationTest._get_fixture_data('g6_service_fixture.json') @staticmethod def _get_framework_fixture_data(framework_slug): return { 'frameworks': next(f for f in BaseApplicationTest. _get_frameworks_list_fixture_data()['frameworks'] if f['slug'] == framework_slug) } @staticmethod def _get_dos_brief_fixture_data(multi=False): if multi: return BaseApplicationTest._get_fixture_data( 'dos_multiple_briefs_fixture.json') else: return BaseApplicationTest._get_fixture_data( 'dos_brief_fixture.json') @staticmethod def _get_dos_brief_search_api_response_fixture_data(): return BaseApplicationTest._get_fixture_data( 'dos_brief_search_api_response.json') @staticmethod def _get_dos_brief_search_api_aggregations_response_outcomes_fixture_data( ): return BaseApplicationTest._get_fixture_data( 'dos_brief_search_api_aggregations_response_outcomes.json') @staticmethod def _get_dos_brief_search_api_aggregations_response_specialists_fixture_data( ): return BaseApplicationTest._get_fixture_data( 'dos_brief_search_api_aggregations_response_specialists.json') @staticmethod def _get_dos_brief_search_api_aggregations_response_user_research_fixture_data( ): return BaseApplicationTest._get_fixture_data( 'dos_brief_search_api_aggregations_response_user_research.json') @staticmethod def _get_dos_brief_responses_fixture_data(): return BaseApplicationTest._get_fixture_data( 'dos_brief_responses_fixture.json') @staticmethod def _get_supplier_fixture_data(): return BaseApplicationTest._get_fixture_data('supplier_fixture.json') @staticmethod def _get_supplier_with_minimum_fixture_data(): return BaseApplicationTest._get_fixture_data( 'supplier_fixture_with_minium_data.json') @staticmethod def _get_suppliers_by_prefix_fixture_data(): return BaseApplicationTest._get_fixture_data( 'suppliers_by_prefix_fixture.json') @staticmethod def _get_suppliers_by_prefix_fixture_data_page_2(): return BaseApplicationTest._get_fixture_data( 'suppliers_by_prefix_fixture_page_2.json') @staticmethod def _get_suppliers_by_prefix_fixture_with_next_and_prev(): return BaseApplicationTest._get_fixture_data( 'suppliers_by_prefix_fixture_page_with_next_and_prev.json') @staticmethod def _get_direct_award_project_list_fixture(**kwargs): return BaseApplicationTest._get_fixture_data( 'direct_award_project_list_fixture.json') @staticmethod def _get_direct_award_project_list_xss_fixture(**kwargs): return BaseApplicationTest._get_fixture_data( 'direct_award_project_list_xss_fixture.json') @staticmethod def _get_direct_award_project_fixture(**kwargs): project = BaseApplicationTest._get_fixture_data( 'direct_award_project_fixture.json') for key, value in kwargs.items(): if key in project['project']: project['project'][key] = value else: raise ValueError( 'Key "{}" does not exist in the Direct Award project fixture.' .format(key)) return project @staticmethod def _get_direct_award_lock_project_fixture(): return BaseApplicationTest._get_direct_award_project_fixture( lockedAt="2017-09-08T00:00:00.000000Z") @staticmethod def _get_direct_award_not_lock_project_fixture(): return BaseApplicationTest._get_direct_award_project_fixture( lockedAt=False) @staticmethod def _get_direct_award_project_outcome_awarded_fixture(): return BaseApplicationTest._get_fixture_data( 'direct_award_project_outcome_awarded_fixture.json') @staticmethod def _get_direct_award_project_completed_outcome_awarded_fixture(): outcome = BaseApplicationTest._get_direct_award_project_outcome_awarded_fixture( ) outcome['outcome']['completed'] = True return outcome @staticmethod def _get_direct_award_project_with_outcome_awarded_fixture(): project = BaseApplicationTest._get_direct_award_lock_project_fixture() project['project']['outcome'] = \ BaseApplicationTest._get_direct_award_project_outcome_awarded_fixture()['outcome'] return project @staticmethod def _get_direct_award_project_with_completed_outcome_awarded_fixture(): project = BaseApplicationTest._get_direct_award_lock_project_fixture() project['project']['outcome'] = \ BaseApplicationTest._get_direct_award_project_completed_outcome_awarded_fixture()['outcome'] return project @staticmethod def _get_direct_award_project_searches_fixture(only_active=False): searches = BaseApplicationTest._get_fixture_data( 'direct_award_project_searches_fixture.json') if only_active: searches = { 'searches': [ search for search in searches['searches'] if search['active'] ] } return searches @staticmethod def _get_direct_award_project_services_fixture(): return BaseApplicationTest._get_fixture_data( 'direct_award_project_services_fixture.json') @staticmethod def _get_direct_award_project_services_zero_state_fixture(): return BaseApplicationTest._get_fixture_data( 'direct_award_project_services_zero_state_fixture.json') @staticmethod def _strip_whitespace(whitespace_in_this): return re.sub(r"\s+", "", whitespace_in_this, flags=re.UNICODE) @staticmethod def _normalize_whitespace(whitespace_in_this): # NOTE proper xml-standard way of doing this is a little more complex afaik return re.sub(r"\s+", " ", whitespace_in_this, flags=re.UNICODE).strip() @classmethod def _squashed_element_text(cls, element): return element.text + "".join( cls._squashed_element_text(child_element) + child_element.tail for child_element in element) def teardown_login(self): if self.get_user_patch is not None: self.get_user_patch.stop() def login_as_supplier(self): with mock.patch('app.data_api_client') as login_api_client: login_api_client.authenticate_user.return_value = self.user( 123, "*****@*****.**", 1234, u'Supplier NĀme', u'Năme') self.get_user_patch = mock.patch.object( data_api_client, 'get_user', return_value=self.user(123, "*****@*****.**", 1234, u'Supplier NĀme', u'Năme')) self.get_user_patch.start() response = self.client.post("/auto-supplier-login") assert response.status_code == 200 def login_as_buyer(self, user_id=123): with mock.patch('app.data_api_client') as login_api_client: login_api_client.authenticate_user.return_value = self.user( user_id, "*****@*****.**", None, None, 'Ā Buyer', role='buyer') self.get_user_patch = mock.patch.object(data_api_client, 'get_user', return_value=self.user( user_id, "*****@*****.**", None, None, 'Buyer', role='buyer')) self.get_user_patch.start() response = self.client.post("/auto-buyer-login") assert response.status_code == 200 def login_as_admin(self): with mock.patch( 'app.main.views.login.data_api_client') as login_api_client: login_api_client.authenticate_user.return_value = self.user( 123, "*****@*****.**", None, None, 'Name', role='admin') self.get_user_patch = mock.patch.object(data_api_client, 'get_user', return_value=self.user( 123, "*****@*****.**", None, None, 'Some Admin', role='admin')) self.get_user_patch.start() self.client.post("/login", data={ 'email_address': '*****@*****.**', 'password': '******' }) login_api_client.authenticate_user.assert_called_once_with( "*****@*****.**", "1234567890") @staticmethod def get_cookie_by_name(response, name): cookies = response.headers.getlist('Set-Cookie') for cookie in cookies: if name in parse_cookie(cookie): return parse_cookie(cookie) return None @staticmethod def strip_all_whitespace(content): pattern = re.compile(r'\s+') return re.sub(pattern, '', content) @staticmethod def find_search_summary(res_data): return re.findall( r'<span class="app-search-summary__count">.+</span>[^\n]+', res_data) # Method to test flashes taken from http://blog.paulopoiati.com/2013/02/22/testing-flash-messages-in-flask/ def assert_flashes(self, expected_message_markup, expected_category='message'): with self.client.session_transaction() as session: try: category, message = session['_flashes'][0] except KeyError: raise AssertionError('nothing flashed') # The code under test put `message` into the template, and jinja will call `escape` on it. # We need to ensure we didn't wrap something unsafe in a `Markup` object, so should be checking the # output markup, not the message that went in. assert expected_message_markup in escape(message) assert expected_category == category