def test_resource_reference_removal_non_breaking2(self): # Removed resource reference is defined by child type, and it # can be resolved to the same resource with the message options. # Original field has resource reference `example.v1/Foo`. field_options = make_field_annotation_resource_reference( resource_type="example.v1/Foo", is_child_type=False ) field_with_reference = make_field(name="Test", options=field_options) # Update field has no resource reference. But the message has # resource options `example.v1/Foo`. message_resource = make_resource_descriptor( resource_type="example.v1/Foo", resource_patterns=["bar/{bar}"] ) field_resource = make_resource_descriptor( resource_type="example.v1/Foo", resource_patterns=["bar/{bar}/foo/{foo}"] ) # Register the two resources in the database. resource_database = make_resource_database( resources=[message_resource, field_resource] ) field_without_reference = make_field( name="Test", message_resource=message_resource, resource_database=resource_database, ) # `bar/{bar}` is the parent resource of `bar/{bar}/foo/{foo}`. FieldComparator( field_with_reference, field_without_reference, self.finding_container, context="ctx", ).compare() finding = self.finding_container.get_all_findings()[0] self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_MOVED") self.assertEqual(finding.change_type.name, "MINOR")
def test_resource_reference_child_type_addition_non_breaking(self): # The added resource reference is in the database. Non-breaking change. # The original field is without resource reference. field_without_reference = make_field(name="Test") # Create a database with resource `example.v1/Foo` registered. resource = make_resource_descriptor( resource_type="example.v1/Foo", resource_patterns=["foo/{foo}"] ) resource_child = make_resource_descriptor( resource_type="example.v1/Bar", resource_patterns=["foo/{foo}/bar/{bar}"], ) resource_database = make_resource_database(resources=[resource, resource_child]) # The update field has resource reference of child_type `example.v1/Bar`. field_options = desc.FieldOptions() field_options.Extensions[ resource_pb2.resource_reference ].child_type = "example.v1/Bar" field_with_reference = make_field( name="Test", options=field_options, resource_database=resource_database ) FieldComparator( field_without_reference, field_with_reference, self.finding_container, context="ctx", ).compare() finding = self.finding_container.get_all_findings()[0] self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_ADDITION") self.assertEqual(finding.change_type.name, "MINOR")
def test_resource_reference_change_type_conversion_non_breaking(self): child_resource = make_resource_descriptor( resource_type="example.v1/Foo", resource_patterns=["bar/{bar}/foo/{foo}", "bar/{bar}/foo"], ) parent_resource = make_resource_descriptor( resource_type="example.v1/Bar", resource_patterns=["bar/{bar}"] ) # Register two resources in database. resource_database = make_resource_database( resources=[child_resource, parent_resource] ) # The original field is defined by child type. field_options_child = make_field_annotation_resource_reference( resource_type="example.v1/Foo", is_child_type=True ) field_with_reference_child = make_field( name="Test", options=field_options_child, resource_database=resource_database, ) # The update field is defined by parent type. field_options_parent = make_field_annotation_resource_reference( resource_type="example.v1/Bar", is_child_type=False ) field_with_reference_parent = make_field( name="Test", options=field_options_parent, resource_database=resource_database, ) # The two resources can be resolved to the identical resource. FieldComparator( field_with_reference_child, field_with_reference_parent, self.finding_container, context="ctx", ).compare() finding = self.finding_container.get_all_findings() # No breaking change should be detected. self.assertFalse(finding) # Reverse should be same since the two resources can # be resolved to the identical resource. FieldComparator( field_with_reference_parent, field_with_reference_child, self.finding_container, context="ctx", ).compare() finding = self.finding_container.get_all_findings() # No breaking change should be detected. self.assertFalse(finding)
def test_resource_reference_removal_non_breaking1(self): # Removed resource reference is defined by type, and it is # added back to the message options. # Original field has resource reference `example.v1/Foo`. field_options = make_field_annotation_resource_reference( resource_type="example.v1/Foo", is_child_type=False ) field_with_reference = make_field(name="Test", options=field_options) # Update field has no resource reference. But the message has # resource options `example.v1/Foo`. message_resource = make_resource_descriptor( resource_type="example.v1/Foo", resource_patterns=["bar/{bar}"] ) field_without_reference = make_field( name="Test", message_resource=message_resource ) FieldComparator( field_with_reference, field_without_reference, self.finding_container, context="ctx", ).compare() finding = self.finding_container.get_all_findings()[0] self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_MOVED") self.assertEqual(finding.change_type.name, "MINOR")
def test_resource_reference_removal_breaking2(self): # Removed resource reference is defined by type, which is not identical # with the message options. # Original field has resource reference `example.v1/Foo`. field_options = make_field_annotation_resource_reference( resource_type="example.v1/Foo", is_child_type=False ) field_with_reference = make_field(name="Test", options=field_options) # Update field has no resource reference, and the resource type # is different from the message options type. message_resource = make_resource_descriptor( resource_type="NotInteresting", resource_patterns=["NotInteresting"] ) field_without_reference = make_field( name="Test", message_resource=message_resource ) FieldComparator( field_with_reference, field_without_reference, self.finding_container, context="ctx", ).compare() finding = self.finding_container.get_actionable_findings()[0] self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_REMOVAL") self.assertEqual(finding.change_type.name, "MAJOR")
def test_get_resource_by_type(self): resource = make_resource_descriptor(resource_type="resource", resource_patterns=["a"]) self.resource_database.register_resource(resource) self.assertEqual( self.resource_database.get_resource_by_type("resource"), resource) self.assertEqual( self.resource_database.get_resource_by_type("resource1"), None)
def test_register_invalid_resource(self): # Register resource with no type. resource_without_type = make_resource_descriptor( resource_type=None, resource_patterns=["a"]) # Raise TypeError: APIs must define a resource type # and resource pattern for each resource in the API. with self.assertRaises(TypeError): self.resource_database.register_resource(resource_without_type)
def test_get_resource_by_pattern(self): resource = make_resource_descriptor(resource_type="resource", resource_patterns=["a", "b"]) self.resource_database.register_resource(resource) # The resourc could be query by either type. self.assertEqual(self.resource_database.get_resource_by_pattern("a"), resource) self.assertEqual(self.resource_database.get_resource_by_pattern("b"), resource)
def test_register_valid_resource(self): resource = make_resource_descriptor(resource_type="resource", resource_patterns=["a"]) self.resource_database.register_resource(resource) self.assertEqual(list(self.resource_database.types.keys()), ["resource"]) self.assertEqual(self.resource_database.types["resource"], resource) self.assertEqual(list(self.resource_database.patterns.keys()), ["a"]) self.assertEqual(self.resource_database.patterns["a"], resource)
def test_register_resource_with_multiple_patterns(self): resource = make_resource_descriptor(resource_type="resource", resource_patterns=["b", "c"]) self.resource_database.register_resource(resource) self.assertEqual(list(self.resource_database.types.keys()), ["resource"]) self.assertEqual(self.resource_database.types["resource"], resource) self.assertEqual(list(self.resource_database.patterns.keys()), ["b", "c"]) self.assertEqual(self.resource_database.patterns["b"], resource) self.assertEqual(self.resource_database.patterns["c"], resource)
def test_resource_reference_change_type_conversion_breaking(self): resource_bar = make_resource_descriptor( resource_type="example.v1/Bar", resource_patterns=["bar/{bar}/foo/{foo}", "bar/{bar}/foo"], ) resource_foo = make_resource_descriptor( resource_type="example.v1/Foo", resource_patterns=["foo/{foo}"] ) # Register two resources in database. resource_database = make_resource_database( resources=[resource_bar, resource_foo] ) # The original field is defined by child type. field_options_child = make_field_annotation_resource_reference( resource_type="example.v1/Bar", is_child_type=True ) field_with_reference_child = make_field( name="Test", options=field_options_child, resource_database=resource_database, ) # The update field is defined by parent type. field_options_parent = make_field_annotation_resource_reference( resource_type="example.v1/Foo", is_child_type=False ) field_with_reference_parent = make_field( name="Test", options=field_options_parent, resource_database=resource_database, ) # The two resources can nnot be resolved to the identical resource. FieldComparator( field_with_reference_child, field_with_reference_parent, self.finding_container, context="ctx", ).compare() finding = self.finding_container.get_all_findings()[0] self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_CHANGE_CHILD_TYPE") self.assertEqual(finding.change_type.name, "MAJOR")
def test_get_parent_resource_by_type(self): child_resource = make_resource_descriptor( resource_type="child", resource_patterns=["a/{a}/b/{b}", "b/{b}"]) parent_resource = make_resource_descriptor( resource_type="parent", resource_patterns=["a/{a}/b"]) self.resource_database.register_resource(child_resource) self.resource_database.register_resource(parent_resource) # Check None child type, should return []. parent_resources = self.resource_database.get_parent_resources_by_child_type( None) self.assertFalse(parent_resources) # `a/{a}/b` is the parent pattern of `a/{a}/b/{b}` parent_resources = self.resource_database.get_parent_resources_by_child_type( "child") self.assertIn(parent_resource, parent_resources) # Reverse query would not have any result. parent_resources = self.resource_database.get_parent_resources_by_child_type( "parent") self.assertFalse(parent_resources) # Search parent resource for an non-existing resource, should return []. parent_resources = self.resource_database.get_parent_resources_by_child_type( "non-existing") self.assertFalse(parent_resources)
def test_resource_reference_removal_breaking3(self): # Removed resource reference is defined by child type, which can not # be resolved to identical resource with the message options. # Original field has resource reference `example.v1/Foo`. field_options = make_field_annotation_resource_reference( resource_type="example.v1/Foo", is_child_type=True ) field_with_reference = make_field(name="Test", options=field_options) # Update field has no resource reference, and the removed resource child type # is not identical with the message resource option. message_resource = make_resource_descriptor( resource_type="example.v1/Bar", resource_patterns=["bar/{bar}"] ) field_resource = make_resource_descriptor( resource_type="example.v1/Foo", resource_patterns=["foo/{foo}"] ) # Register the two resources in the database. resource_database = make_resource_database( resources=[message_resource, field_resource] ) field_without_reference = make_field( name="Test", resource_database=resource_database, message_resource=message_resource, ) FieldComparator( field_with_reference, field_without_reference, self.finding_container, context="ctx", ).compare() # `bar/{bar}` is not parent resource of `foo/{foo}`. finding = self.finding_container.get_actionable_findings()[0] self.assertEqual(finding.category.name, "RESOURCE_REFERENCE_REMOVAL") self.assertEqual(finding.change_type.name, "MAJOR")