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]))
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'])
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])
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'])
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)
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)
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)
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
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)
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)
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'])
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'])
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'], {})