def test_validation_failure_sanitizes_error_section_secrets(
            self, mock_jsonschema):
        m_args = mock.Mock()
        mock_jsonschema.Draft4Validator(m_args).iter_errors.side_effect = [
            # Return empty list of errors for base schema and metadata
            # validator and pretend that 1 error is returned for next
            # validator.
            [],
            [],
            [mock.Mock(path=[], schema_path=[], message='scary-secret-here')]
        ]

        document_factory = factories.DocumentFactory(1, [1])
        test_document = document_factory.gen_test(
            {
                '_GLOBAL_DATA_1_': {'data': {'secret-a': 5}},
                '_GLOBAL_SUBSTITUTIONS_1_': [
                    {'src': {
                        'path': '.', 'schema': 'foo/bar/v1', 'name': 'foo'},
                     'dest': {'path': '.secret-a'}}
                ]
            },
            global_abstract=False)[-1]

        data_schema_factory = factories.DataSchemaFactory()
        data_schema = data_schema_factory.gen_test(test_document['schema'], {})

        validations = document_validation.DocumentValidation(
            test_document, existing_data_schemas=[data_schema],
            pre_validate=False).validate_all()

        self.assertEqual(1, len(validations[0]['errors']))
        self.assertIn('Sanitized to avoid exposing secret.',
                      str(validations[0]['errors'][-1]))
        self.assertNotIn('scary-secret.', str(validations[0]['errors'][-1]))
Beispiel #2
0
    def test_document_without_data_section_ingested(self):
        """Validate that a document without the data section is ingested
        successfully.
        """
        rules = {'deckhand:create_cleartext_documents': '@',
                 'deckhand:list_validations': '@'}
        self.policy.set_rules(rules)

        documents_factory = factories.DocumentFactory(1, [1])
        document = documents_factory.gen_test({}, global_abstract=False)[-1]
        del document['data']

        data_schema_factory = factories.DataSchemaFactory()
        data_schema = data_schema_factory.gen_test(document['schema'], {})

        revision_id = self._create_revision(payload=[document, data_schema])

        # Validate that the entry is present.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations/%s' % (
                revision_id, types.DECKHAND_SCHEMA_VALIDATION),
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)

        body = yaml.safe_load(resp.text)
        expected_body = {
            'count': 2,
            'results': [{'id': 0, 'status': 'success'},  # Document.
                        {'id': 1, 'status': 'success'}]  # DataSchema.
        }
        self.assertEqual(expected_body, body)
    def test_validation_failure_sanitizes_message_secrets(self):
        data_schema_factory = factories.DataSchemaFactory()
        metadata_name = 'example/Doc/v1'
        schema_to_use = {
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'secret-a': {'type': 'string'}
            },
            'required': ['secret-a'],
            'additionalProperties': False
        }
        data_schema = data_schema_factory.gen_test(
            metadata_name, data=schema_to_use)

        # Case 1: Check that sensitive data is sanitized if the document has
        # substitutions and `metadata.storagePolicy` == 'cleartext'.
        document_factory = factories.DocumentFactory(1, [1])
        test_document = document_factory.gen_test({
            "_GLOBAL_DATA_1_": {'data': {'secret-a': 5}},
            "_GLOBAL_SCHEMA_1_": metadata_name,
            "_GLOBAL_SUBSTITUTIONS_1_": [{
                "dest": {
                    "path": ".secret-a"
                },
                "src": {
                    "schema": "deckhand/CertificateKey/v1",
                    "name": "site-cert",
                    "path": "."
                }
            }],
        }, global_abstract=False)[-1]
        test_document['metadata']['storagePolicy'] = 'cleartext'

        validations = document_validation.DocumentValidation(
            test_document, existing_data_schemas=[data_schema],
            pre_validate=False).validate_all()

        self.assertEqual(1, len(validations[0]['errors']))
        self.assertEqual('Sanitized to avoid exposing secret.',
                         validations[0]['errors'][0]['message'])

        # Case 2: Check that sensitive data is sanitized if the document has
        # no substitutions and `metadata.storagePolicy` == 'encrypted'.
        test_document = document_factory.gen_test({
            "_GLOBAL_DATA_1_": {'data': {'secret-a': 5}},
            "_GLOBAL_SCHEMA_1_": metadata_name,
            "_GLOBAL_SUBSTITUTIONS_1_": [],
        }, global_abstract=False)[-1]
        test_document['metadata']['storagePolicy'] = 'encrypted'

        validations = document_validation.DocumentValidation(
            test_document, existing_data_schemas=[data_schema],
            pre_validate=False).validate_all()

        self.assertEqual(1, len(validations[0]['errors']))
        self.assertEqual('Sanitized to avoid exposing secret.',
                         validations[0]['errors'][0]['message'])
Beispiel #4
0
    def test_put_bucket_with_secret(self):
        def _do_test(payload):
            bucket_name = test_utils.rand_name('bucket')
            resp = self.app.simulate_put(
                '/api/v1.0/buckets/%s/documents' % bucket_name,
                headers={'Content-Type': 'application/x-yaml'},
                body=yaml.safe_dump_all(payload))
            self.assertEqual(200, resp.status_code)
            created_documents = list(yaml.safe_load_all(resp.text))

            self.assertEqual(len(payload), len(created_documents))
            expected = sorted([(d['schema'], d['metadata']['name'])
                               for d in payload])
            actual = sorted([(d['schema'], d['metadata']['name'])
                             for d in created_documents])
            self.assertEqual(expected, actual)
            self.assertEqual(payload[0]['data'], created_documents[0]['data'])

        # Verify whether creating a cleartext secret works.
        rules = {'deckhand:create_cleartext_documents': '@'}
        self.policy.set_rules(rules)

        secrets_factory = factories.DocumentSecretFactory()
        payload = [secrets_factory.gen_test('Certificate', 'cleartext')]
        _do_test(payload)

        # Verify whether creating an encrypted secret works.
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:create_encrypted_documents': '@'
        }
        self.policy.set_rules(rules)

        secrets_factory = factories.DocumentSecretFactory()
        payload = [secrets_factory.gen_test('Certificate', 'encrypted')]

        with mock.patch.object(secrets_manager,
                               'SecretsManager',
                               autospec=True) as mock_secrets_mgr:
            mock_secrets_mgr.create.return_value = payload[0]['data']
            _do_test(payload)

        # Verify whether any document can be encrypted if its
        # `metadata.storagePolicy`='encrypted'. In the case below,
        # a generic document is tested.
        documents_factory = factories.DocumentFactory(1, [1])
        document = documents_factory.gen_test({}, global_abstract=False)[-1]
        document['metadata']['storagePolicy'] = 'encrypted'

        data_schema_factory = factories.DataSchemaFactory()
        data_schema = data_schema_factory.gen_test(document['schema'], {})

        with mock.patch.object(secrets_manager,
                               'SecretsManager',
                               autospec=True) as mock_secrets_mgr:
            mock_secrets_mgr.create.return_value = document['data']
            _do_test([document, data_schema])
Beispiel #5
0
    def test_validation_data_schema_same_revision_expect_failure(self):
        """Validates that creating a ``DataSchema`` alongside a document
        that relies on it in the same revision results in an expected failure.
        """
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:list_validations': '@'
        }
        self.policy.set_rules(rules)

        # Create a `DataSchema` against which the test document will be
        # validated.
        data_schema_factory = factories.DataSchemaFactory()
        metadata_name = 'example/foo/v1'
        schema_to_use = {
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'a': {
                    'type': 'integer'  # Test doc will fail b/c of wrong type.
                }
            },
            'required': ['a']
        }
        data_schema = data_schema_factory.gen_test(metadata_name,
                                                   data=schema_to_use)

        # Create the test document that fails the validation due to the
        # schema defined by the `DataSchema` document.
        doc_factory = factories.DocumentFactory(1, [1])
        doc_to_test = doc_factory.gen_test(
            {'_GLOBAL_DATA_1_': {
                'data': {
                    'a': 'fail'
                }
            }},
            global_abstract=False)[-1]
        doc_to_test['schema'] = 'example/foo/v1'
        doc_to_test['metadata']['name'] = 'test_doc'

        revision_id = self._create_revision(payload=[doc_to_test, data_schema])

        # Validate that the validation was created and reports failure.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations' % revision_id,
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            1,
            'results': [{
                'name': types.DECKHAND_SCHEMA_VALIDATION,
                'status': 'failure'
            }]
        }
        self.assertEqual(expected_body, body)
    def test_parent_selector_and_actions_both_provided_is_valid(self):
        test_document = self._read_data('sample_document')
        data_schema_factory = factories.DataSchemaFactory()
        data_schema = data_schema_factory.gen_test(test_document['schema'], {})

        validations = document_validation.DocumentValidation(
            test_document, existing_data_schemas=[data_schema],
            pre_validate=False).validate_all()

        self.assertEmpty(validations[0]['errors'])
Beispiel #7
0
    def test_pre_validate_flag_skips_over_dataschema_validations(self):
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:list_validations': '@'
        }
        self.policy.set_rules(rules)

        # Create a `DataSchema` against which the test document will be
        # validated.
        data_schema_factory = factories.DataSchemaFactory()
        metadata_name = 'example/foo/v1'
        schema_to_use = {
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'a': {
                    'type': 'integer'  # Test doc will fail b/c of wrong type.
                }
            },
            'required': ['a']
        }
        data_schema = data_schema_factory.gen_test(metadata_name,
                                                   data=schema_to_use)

        # Create a document that passes validation and another that fails it.
        doc_factory = factories.DocumentFactory(1, [1])
        fail_doc = doc_factory.gen_test(
            {'_GLOBAL_DATA_1_': {
                'data': {
                    'a': 'fail'
                }
            }},
            global_abstract=False)[-1]
        fail_doc['schema'] = 'example/foo/v1'
        fail_doc['metadata']['name'] = 'test_doc'

        revision_id = self._create_revision(payload=[data_schema, fail_doc])

        # Validate that the validation reports success because `fail_doc`
        # isn't validated by the `DataSchema`.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations' % revision_id,
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            1,
            'results': [{
                'name': types.DECKHAND_SCHEMA_VALIDATION,
                'status': 'success'
            }]
        }
        self.assertEqual(expected_body, body)
Beispiel #8
0
    def test_rendered_documents_fail_post_validation(self):
        """Validates that when fully rendered documents fail schema validation,
        a 400 is raised.

        For this scenario a DataSchema checks that the relevant document has
        a key in its data section, a key which is removed during the rendering
        process as the document uses a delete action. This triggers
        post-rendering validation failure.
        """
        rules = {'deckhand:list_cleartext_documents': '@',
                 'deckhand:list_encrypted_documents': '@',
                 'deckhand:create_cleartext_documents': '@'}
        self.policy.set_rules(rules)

        # Create a document for a bucket.
        documents_factory = factories.DocumentFactory(2, [1, 1])
        payload = documents_factory.gen_test({
            "_GLOBAL_DATA_1_": {"data": {"a": "b"}},
            "_SITE_DATA_1_": {"data": {"a": "b"}},
            "_SITE_ACTIONS_1_": {
                "actions": [{"method": "delete", "path": "."}]
            }
        }, site_abstract=False)

        data_schema_factory = factories.DataSchemaFactory()
        metadata_name = payload[-1]['schema']
        schema_to_use = {
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'a': {
                    'type': 'string'
                }
            },
            'required': ['a'],
            'additionalProperties': False
        }
        data_schema = data_schema_factory.gen_test(
            metadata_name, data=schema_to_use)
        payload.append(data_schema)

        resp = self.app.simulate_put(
            '/api/v1.0/buckets/mop/documents',
            headers={'Content-Type': 'application/x-yaml'},
            body=yaml.safe_dump_all(payload))
        self.assertEqual(200, resp.status_code)
        revision_id = list(yaml.safe_load_all(resp.text))[0]['status'][
            'revision']

        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/rendered-documents' % revision_id,
            headers={'Content-Type': 'application/x-yaml'})

        self.assertEqual(400, resp.status_code)
Beispiel #9
0
    def test_validation_with_registered_data_schema(self):
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:list_validations': '@'
        }
        self.policy.set_rules(rules)

        # Create a `DataSchema` against which the test document will be
        # validated.
        data_schema_factory = factories.DataSchemaFactory()
        metadata_name = 'example/Doc/v1'
        schema_to_use = {
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'a': {
                    'type': 'string'
                }
            },
            'required': ['a'],
            'additionalProperties': False
        }
        data_schema = data_schema_factory.gen_test(metadata_name,
                                                   data=schema_to_use)

        # Create the test document whose data section adheres to the
        # `DataSchema` above.
        doc_factory = factories.DocumentFactory(1, [1])
        doc_to_test = doc_factory.gen_test(
            {'_GLOBAL_DATA_1_': {
                'data': {
                    'a': 'whatever'
                }
            }},
            global_abstract=False)[-1]
        doc_to_test['schema'] = 'example/Doc/v1'

        revision_id = self._create_revision(payload=[doc_to_test, data_schema])

        # Validate that the validation was created and succeeded.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations' % revision_id,
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            1,
            'results': [{
                'name': types.DECKHAND_SCHEMA_VALIDATION,
                'status': 'success'
            }]
        }
        self.assertEqual(expected_body, body)
    def test_neither_parent_selector_nor_actions_provided_is_valid(self):
        test_document = self._read_data('sample_document')
        test_document['metadata']['layeringDefinition'].pop('actions')
        test_document['metadata']['layeringDefinition'].pop('parentSelector')

        data_schema_factory = factories.DataSchemaFactory()
        data_schema = data_schema_factory.gen_test(test_document['schema'], {})

        validations = document_validation.DocumentValidation(
            test_document, existing_data_schemas=[data_schema],
            pre_validate=False).validate_all()

        self.assertEmpty(validations[0]['errors'])
    def test_document_invalid_layering_definition_action(self):
        document = self._read_data('sample_document')
        missing_data = self._corrupt_data(
            document,
            'metadata.layeringDefinition.actions.0.method',
            'invalid',
            op='replace')
        expected_err = "'invalid' is not one of ['replace', 'delete', 'merge']"

        # Ensure that a dataschema document exists for the random document
        # schema via mocking.
        dataschema_factory = factories.DataSchemaFactory()
        dataschema = dataschema_factory.gen_test(document['schema'], {})
        payload = [dataschema, missing_data]
        doc_validator = document_validation.DocumentValidation(payload)
        self._do_validations(doc_validator, document, expected_err)
Beispiel #12
0
    def _create_revision(self, payload=None):
        if not payload:
            documents_factory = factories.DocumentFactory(1, [1])
            payload = documents_factory.gen_test({})
            data_schema_factory = factories.DataSchemaFactory()
            data_schema = data_schema_factory.gen_test(payload[1]['schema'],
                                                       data={})
            payload.append(data_schema)

        resp = self.app.simulate_put(
            '/api/v1.0/buckets/mop/documents',
            headers={'Content-Type': 'application/x-yaml'},
            body=yaml.safe_dump_all(payload))
        self.assertEqual(200, resp.status_code)
        revision_id = list(yaml.safe_load_all(
            resp.text))[0]['status']['revision']
        return revision_id
Beispiel #13
0
    def test_substitution_with_generic_document_as_source(self):
        src_data = 'data-from-generic-document'

        # Create DataSchema document to register generic source document.
        dataschema_factory = factories.DataSchemaFactory()
        dataschema = dataschema_factory.gen_test('unusual/DictWithSecret/v1',
                                                 {})
        # Create the generic source document from which data will be extracted.
        generic_document_mapping = {
            "_GLOBAL_DATA_1_": {
                'data': {
                    'public': 'random',
                    'money': src_data
                }
            }
        }
        payload = self.document_factory.gen_test(generic_document_mapping,
                                                 global_abstract=False)
        payload[-1]['schema'] = "unusual/DictWithSecret/v1"
        payload[-1]['metadata']['name'] = 'dict-with-secret'

        # Store both documents to be created by helper.
        dependent_documents = [payload[-1], dataschema]

        # Mapping for destination document.
        document_mapping = {
            "_GLOBAL_DATA_1_": {
                'data': {}
            },
            "_GLOBAL_SUBSTITUTIONS_1_": [{
                "dest": {
                    "path": "."
                },
                "src": {
                    "schema": "unusual/DictWithSecret/v1",
                    "name": "dict-with-secret",
                    "path": ".money"
                }
            }]
        }
        self._test_doc_substitution(document_mapping,
                                    dependent_documents,
                                    expected_data=src_data)
    def _test_missing_required_sections(self, document, properties_to_remove):
        for idx, property_to_remove in enumerate(properties_to_remove):
            missing_prop = property_to_remove.split('.')[-1]
            invalid_data = self._corrupt_data(document, property_to_remove)

            exception_raised = self.exception_map.get(property_to_remove, None)
            expected_err_msg = "'%s' is a required property" % missing_prop

            dataschema_factory = factories.DataSchemaFactory()
            dataschema = dataschema_factory.gen_test(
                invalid_data.get('schema', ''), {})
            payload = [dataschema, invalid_data]

            doc_validator = document_validation.DocumentValidation(payload)
            if exception_raised:
                self.assertRaises(exception_raised, doc_validator.validate_all)
            else:
                self._do_validations(doc_validator, invalid_data,
                                     expected_err_msg)
Beispiel #15
0
    def test_validation_only_new_data_schema_registered(self):
        """Validate whether newly created DataSchemas replace old DataSchemas
        when it comes to validation.
        """
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:list_validations': '@'
        }
        self.policy.set_rules(rules)

        # Create 2 DataSchemas that will fail if they're used. These shouldn't
        # be used for validation.
        data_schema_factory = factories.DataSchemaFactory()
        metadata_names = ['exampleA/Doc/v1', 'exampleB/Doc/v1']
        schemas_to_use = [{
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'a': {
                    'type': 'integer'
                }
            },
            'required': ['a'],
            'additionalProperties': False
        }] * 2
        old_data_schemas = [
            data_schema_factory.gen_test(metadata_names[i],
                                         data=schemas_to_use[i])
            for i in range(2)
        ]
        # Save the DataSchemas in the first revision.
        revision_id = self._create_revision(payload=old_data_schemas)

        # Create 2 DataSchemas that will pass if they're used. These should
        # be used for validation.
        for schema_to_use in schemas_to_use:
            schema_to_use['properties']['a']['type'] = 'string'
        new_data_schemas = [
            data_schema_factory.gen_test(metadata_names[i],
                                         data=schemas_to_use[i])
            for i in range(2)
        ]
        doc_factory = factories.DocumentFactory(1, [1])
        example1_doc = doc_factory.gen_test(
            {'_GLOBAL_DATA_1_': {
                'data': {
                    'a': 'whatever'
                }
            }},
            global_abstract=False)[-1]
        example1_doc['schema'] = metadata_names[0]
        example2_doc = copy.deepcopy(example1_doc)
        example2_doc['schema'] = metadata_names[1]
        # Save the documents that will be validated alongside the DataSchemas
        # that will be used to validate them.
        revision_id = self._create_revision(
            payload=[example1_doc, example2_doc] + new_data_schemas)

        # Validate that the validation was created and succeeded: This means
        # that the new DataSchemas were used, not the old ones.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations' % revision_id,
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            1,
            'results': [{
                'name': types.DECKHAND_SCHEMA_VALIDATION,
                'status': 'success'
            }]
        }
        self.assertEqual(expected_body, body)
Beispiel #16
0
    def test_document_without_data_section_saves_but_fails_validation(self):
        """Validate that a document without the data section is saved to the
        database, but fails validation. This is a valid use case because a
        document in a bucket can be created without a data section, which
        depends on substitution from another document.
        """
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:list_validations': '@',
            'deckhand:show_validation': '@'
        }
        self.policy.set_rules(rules)

        documents_factory = factories.DocumentFactory(1, [1])
        document = documents_factory.gen_test({}, global_abstract=False)[-1]
        del document['data']

        data_schema_factory = factories.DataSchemaFactory()
        data_schema = data_schema_factory.gen_test(document['schema'], {})

        revision_id = self._create_revision(payload=[document, data_schema])

        # Validate that the entry is present.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations/%s' %
            (revision_id, types.DECKHAND_SCHEMA_VALIDATION),
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)

        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            2,
            'results': [
                {
                    'id': 0,
                    'status': 'failure'
                },  # Document.
                {
                    'id': 1,
                    'status': 'success'
                }
            ]  # DataSchema.
        }
        self.assertEqual(expected_body, body)

        # Validate that the created document failed validation for the expected
        # reason.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations/%s/entries/0' %
            (revision_id, types.DECKHAND_SCHEMA_VALIDATION),
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_errors = [{
            'error_section': {
                'data': None,
                'metadata': {
                    'labels': {
                        'global': 'global1'
                    },
                    'layeringDefinition': {
                        'abstract': False,
                        'actions': [],
                        'layer': 'global'
                    },
                    'name': document['metadata']['name'],
                    'schema': 'metadata/Document/v1.0'
                },
                'schema': document['schema']
            },
            'name':
            document['metadata']['name'],
            'path':
            '.data',
            'schema':
            document['schema'],
            'message':
            ("None is not of type 'string', 'integer', 'array', 'object'"),
            'validation_schema':
            document_schema.schema,
            'schema_path':
            '.properties.data.type'
        }]
        self.assertIn('errors', body)
        self.assertEqual(expected_errors, body['errors'])
Beispiel #17
0
    def test_validation_with_registered_data_schema_expect_mixed(self):
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:list_validations': '@',
            'deckhand:show_validation': '@'
        }
        self.policy.set_rules(rules)

        # Create a `DataSchema` against which the test document will be
        # validated.
        data_schema_factory = factories.DataSchemaFactory()
        metadata_name = 'example/foo/v1'
        schema_to_use = {
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'a': {
                    'type': 'integer'  # Test doc will fail b/c of wrong type.
                }
            },
            'required': ['a']
        }
        expected_errors = [{
            'error_section': {
                'a': 'fail'
            },
            'name': 'test_doc',
            'path': '.data.a',
            'schema': 'example/foo/v1',
            'message': "'fail' is not of type 'integer'",
            'validation_schema': schema_to_use,
            'schema_path': '.properties.a.type'
        }]
        data_schema = data_schema_factory.gen_test(metadata_name,
                                                   data=schema_to_use)

        # Create a document that passes validation and another that fails it.
        doc_factory = factories.DocumentFactory(1, [1])
        fail_doc = doc_factory.gen_test(
            {'_GLOBAL_DATA_1_': {
                'data': {
                    'a': 'fail'
                }
            }},
            global_abstract=False)[-1]
        fail_doc['schema'] = 'example/foo/v1'
        fail_doc['metadata']['name'] = 'test_doc'

        pass_doc = copy.deepcopy(fail_doc)
        pass_doc['data']['a'] = 5

        revision_id = self._create_revision(
            payload=[fail_doc, pass_doc, data_schema])

        # Validate that the validation reports failure since `fail_doc`
        # should've failed validation.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations' % revision_id,
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            1,
            'results': [{
                'name': types.DECKHAND_SCHEMA_VALIDATION,
                'status': 'failure'
            }]
        }
        self.assertEqual(expected_body, body)

        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations/%s' %
            (revision_id, types.DECKHAND_SCHEMA_VALIDATION),
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            3,
            'results': [
                {
                    'id': 0,
                    'status': 'failure'
                },  # fail_doc failed.
                {
                    'id': 1,
                    'status': 'success'
                },  # DataSchema passed.
                {
                    'id': 2,
                    'status': 'success'
                }
            ]  # pass_doc succeeded.
        }
        self.assertEqual(expected_body, body)

        # Validate that fail_doc validation failed for the expected reason.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations/%s/entries/0' %
            (revision_id, types.DECKHAND_SCHEMA_VALIDATION),
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_errors = [{
            'error_section': {
                'a': 'fail'
            },
            'name': 'test_doc',
            'path': '.data.a',
            'schema': 'example/foo/v1',
            'message': "'fail' is not of type 'integer'",
            'validation_schema': schema_to_use,
            'schema_path': '.properties.a.type'
        }]

        self.assertIn('errors', body)
        self.assertEqual(expected_errors, body['errors'])
Beispiel #18
0
    def test_validation_with_registered_data_schema_expect_multi_failure(self):
        rules = {
            'deckhand:create_cleartext_documents': '@',
            'deckhand:list_validations': '@',
            'deckhand:show_validation': '@'
        }
        self.policy.set_rules(rules)

        # Create a `DataSchema` against which the test document will be
        # validated.
        data_schema_factory = factories.DataSchemaFactory()
        metadata_name = 'example/foo/v1'
        schema_to_use = {
            '$schema': 'http://json-schema.org/schema#',
            'type': 'object',
            'properties': {
                'a': {
                    'type': 'integer'  # Test doc will fail b/c of wrong type.
                }
            },
            'required': ['a']
        }
        data_schema = data_schema_factory.gen_test(metadata_name,
                                                   data=schema_to_use)

        # Failure #1.
        # Create the test document that fails the validation due to the
        # schema defined by the `DataSchema` document.
        doc_factory = factories.DocumentFactory(1, [1])
        doc_to_test = doc_factory.gen_test(
            {'_GLOBAL_DATA_1_': {
                'data': {
                    'a': 'fail'
                }
            }},
            global_abstract=False)[-1]
        doc_to_test['schema'] = 'example/foo/v1'
        doc_to_test['metadata']['name'] = 'test_doc'
        # Failure #2.
        # Remove required metadata property, causing error to be generated.
        del doc_to_test['metadata']['layeringDefinition']

        revision_id = self._create_revision(payload=[doc_to_test, data_schema])

        # Validate that the validation was created and reports failure.
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations' % revision_id,
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)
        expected_body = {
            'count':
            1,
            'results': [{
                'name': types.DECKHAND_SCHEMA_VALIDATION,
                'status': 'failure'
            }]
        }
        self.assertEqual(expected_body, body)

        # Validate that both expected errors are present for validation.
        expected_errors = [{
            'error_section': {
                'data': {
                    'a': 'fail'
                },
                'metadata': {
                    'labels': {
                        'global': 'global1'
                    },
                    'name': 'test_doc',
                    'schema': 'metadata/Document/v1.0'
                },
                'schema': 'example/foo/v1'
            },
            'name': 'test_doc',
            'path': '.metadata',
            'schema': 'example/foo/v1',
            'message': "'layeringDefinition' is a required property",
            'validation_schema': document_schema.schema,
            'schema_path': '.properties.metadata.required'
        }, {
            'error_section': {
                'a': 'fail'
            },
            'name': 'test_doc',
            'path': '.data.a',
            'schema': 'example/foo/v1',
            'message': "'fail' is not of type 'integer'",
            'validation_schema': schema_to_use,
            'schema_path': '.properties.a.type'
        }]
        resp = self.app.simulate_get(
            '/api/v1.0/revisions/%s/validations/%s/entries/0' %
            (revision_id, types.DECKHAND_SCHEMA_VALIDATION),
            headers={'Content-Type': 'application/x-yaml'})
        self.assertEqual(200, resp.status_code)
        body = yaml.safe_load(resp.text)

        self.assertEqual('failure', body['status'])
        self.assertEqual(expected_errors, body['errors'])
 def setUp(self):
     super(TestDocumentValidation, self).setUp()
     self.test_document = self._read_data('sample_document')
     dataschema_factory = factories.DataSchemaFactory()
     self.dataschema = dataschema_factory.gen_test(
         self.test_document['schema'], {})