def test_no_change(self, co3_args): client = self._connect(co3_args) inc = self._create_incident(client, {"name": "test"}) uri = "/incidents/%d" % inc['id'] # Create a conflict inc["name"] = "the wrong value" inc["vers"] -= 1 # Force it to check old_value patch = resilient.Patch(inc) patch.add_value("name", "test updated") def mycb(response, patch_status, patch): raise resilient.NoChange response = client.patch_with_callback(uri, patch, mycb) assert response assert response.status_code == 200 patch_status = resilient.PatchStatus(response.json()) assert not patch_status.is_success() assert patch_status.get_conflict_fields() == ["name"]
def _patch_incident(self, incident_id, incident_payload): """ _patch_incident will update an incident with the specified json payload. :param incident_id: incident ID of incident to be updated. ;param incident_payload: incident fields to be updated. :return: """ try: # Update incident incident_url = "/incidents/{0}".format(incident_id) incident = self.rest_client.get(incident_url) patch = resilient.Patch(incident) # Iterate over payload dict. for name, _ in incident_payload.items(): if name == 'properties': for field_name, field_value in incident_payload[ 'properties'].items(): patch.add_value(field_name, field_value) else: payload_value = incident_payload.get(name) patch.add_value(name, payload_value) patch_result = self.rest_client.patch(incident_url, patch) result = self._chk_status(patch_result) # add back the incident id result['id'] = incident_id return result except Exception as err: raise IntegrationError(err)
def test_patch_invalid_callback(self, co3_args): """ If a callback returns True but didn't modify the passed in patch in any way, that'd be a problem. So make sure we throw an exception in that case. """ client = self._connect(co3_args) inc = self._create_incident(client, {"name": "test"}) uri = "/incidents/%d" % inc['id'] # Create a conflict inc["name"] = "the wrong value" inc["vers"] -= 1 # Force it to check old_value patch = resilient.Patch(inc) patch.add_value("name", "test updated") def mycb(response, patch_status, patch): # Return True but don't modify the patch. return True with pytest.raises(ValueError) as exception_info: client.patch_with_callback(uri, patch, mycb) assert "invoked callback did not change the patch object, but returned True" in str( exception_info.value)
def test_patch(self): existing = {"a": 1, "properties": {"b": 2}, "vers": 99} patch = resilient.Patch(existing) patch.add_value("a", 5) patch.add_value("properties.b", 6) patch.add_value("c", 7) dto = patch.to_dict() assert dto["version"] == 99 changes = dto["changes"] assert len(changes) == 3 change = changes[0] assert change["field"] == "a" assert change["old_value"]["object"] == 1 assert change["new_value"]["object"] == 5 change = changes[1] assert change["field"] == "properties.b" assert change["old_value"]["object"] == 2 assert change["new_value"]["object"] == 6 change = changes[2] assert change["field"] == "c" assert not change["old_value"]["object"] assert change["new_value"]["object"] == 7
def _patch_to_close_incident(res_client, incident_id, close_fields, handle_names=False): """ call the Resilient REST API to patch incident :param res_client: required for communication back to resilient :param incident_id: required :param close_fields: required :return: response object """ uri = "/incidents/{}".format(incident_id) if handle_names: uri = "{0}?handle_format=names".format(uri) previous_object = res_client.get(uri) patch = resilient.Patch(previous_object) for field in close_fields: patch.add_value(field, close_fields[field]) response = res_client.patch(uri, patch) return response
def update_incident(self, payload, existing_incident): """ update_incident will update an incident with the specified json payload. ;param payload: incident fields to be updated. existing_incident ([dict]): existing incident fields, or None for create operation :return: incident updated """ try: # Update incident incident_url = "/".join([INCIDENT_URL, str(existing_incident['id'])]) patch = resilient.Patch(existing_incident) # Iterate over payload dict. for name, _ in payload.items(): if name == 'properties': for field_name, field_value in payload['properties'].items(): patch.add_value(field_name, field_value) else: payload_value = payload.get(name) patch.add_value(name, payload_value) patch_result = self.rest_client.patch(incident_url, patch) result = self._chk_status(patch_result) # add back the incident id result['id'] = existing_incident['id'] return result except Exception as err: raise IntegrationError(err)
def test_exchange_conflicting_value(self): # Given a base object with a value of "test1". base = dict(mytest1="test1") # And a patch that is attempting to modify that base object to have a value of "test2". patch = resilient.Patch(base) patch.add_value("mytest1", "test2") # Confirm that it does indeed have an "old value" of "test1" (this is taken from the base object). assert patch.get_old_value("mytest1") == "test1" # When we create a patch status that simulates a conflict error from the server (where the # value of base changed from "test1" to "blah"). patch_status = resilient.PatchStatus({ "success": False, "field_failures": [{ "field": "mytest1", "your_original_value": "test2", "actual_current_value": "blah" }], "message": "Some message" }) # When I exchange the conflicting value... patch.exchange_conflicting_value(patch_status, "mytest1", "test2") # The patch's "old value" will be the current server's value. assert patch.get_old_value("mytest1") == "blah" assert patch.get_new_value("mytest1") == "test2"
def test_conflict_with_handler(self, co3_args): client = self._connect(co3_args) inc = self._create_incident(client, {"name": "test"}) uri = "/incidents/%d" % inc['id'] # Create a conflict inc["name"] = "the wrong value" inc["vers"] -= 1 # Force it to check old_value patch = resilient.Patch(inc) patch.add_value("name", "test updated") def mycb(response, patch_status, patch): patch.exchange_conflicting_value(patch_status, "name", "test updated take 2") response = client.patch_with_callback(uri, patch, mycb) assert response assert response.status_code == 200 assert "test updated take 2" == client.get(uri)["name"]
def test_has_changes(self): patch = resilient.Patch(dict(testfield=1)) assert not patch.has_changes() patch.add_value("testfield", 5) assert patch.has_changes()
def test_partial_property_name(self): existing = {"properties": {"a": 5}} patch = resilient.Patch(existing) with pytest.raises(ValueError) as exception_info: patch.add_value("properties", 99) assert "Invalid field_name parameter" in str(exception_info.value)
def generic_patch(client, uri, template_file_name): with open(template_file_name, 'r') as update_file: patch = resilient.Patch(json.loads(update_file.read())) response = client.patch(uri, patch) print('Response: ', file=sys.stderr) print(json.dumps(response, indent=4))
def test_patch_no_conflict(self, co3_args): """ do incident_patch with no conflict """ client = self._connect(co3_args) inc = self._create_incident(client, {"name": "test"}) uri = "/incidents/%d" % inc['id'] patch = resilient.Patch(inc) patch.add_value("name", "test updated") response = client.patch(uri, patch, overwrite_conflict=False) assert resilient.PatchStatus(response.json()).is_success() inc = client.get("/incidents/%d" % inc['id']) assert inc['name'] == "test updated"
def test_delete(self): patch = resilient.Patch(dict(a=1, b=2)) assert not patch.has_changes() patch.add_value("a", 11) assert patch.has_changes() assert patch.get_old_value("a") == 1 assert patch.get_new_value("a") == 11 patch.delete_value("a") assert not patch.has_changes()
def test_add_twice(self): patch = resilient.Patch({"a": 5}) patch.add_value("a", 7) patch.add_value("a", 8) mydict = patch.to_dict() changes = mydict["changes"] assert len(changes) == 1 assert changes[0]["field"] == "a" assert changes[0]["old_value"]["object"] == 5 assert changes[0]["new_value"]["object"] == 8
def test_patch_null_old_value(self, co3_args): client = self._connect(co3_args) inc = self._create_incident(client, {"name": "test", "description": None}) patch = resilient.Patch(inc) patch.add_value("description", "new value") uri = "/incidents/%d" % inc['id'] response = client.patch(uri, patch) inc = client.get("/incidents/%d" % inc['id']) assert inc["description"] == "new value"
def test_null_old_value(self): patch = resilient.Patch({"blah": "old value"}) patch.add_value("blah", "new value", old_value=None) mydict = patch.to_dict() assert mydict changes = mydict["changes"] assert changes assert len(changes) == 1 assert changes[0]["field"] == "blah" assert not changes[0]["old_value"]["object"] assert changes[0]["new_value"]["object"] == "new value"
def main(): """ program main """ parser = ExampleArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) inc_id = opts["incid"] desc = opts["desc"] try: uri = '/incidents/{}'.format(inc_id) incident = client.get(uri) # Create a patch object. You need to pass it the base object (the thing being patched). This # object contains the old values, which are sent to the server. patch = resilient.Patch(incident) patch.add_value("description", desc) print(''' At this point, we have a copy of the specified incident. If you want to trigger a conflict to see what will happen, then you can do so now. Press the Enter key to continue''') input() # Apply the patch and overwrite any conflicts. client.patch(uri, patch, overwrite_conflict=True) # Confirm that our change was applied. This is not something that you'd typically need to do since the # patch applied successfully, but this illustrates that the change was applied for the purposes of this # example. assert desc == client.get(uri)["description"] except resilient.SimpleHTTPException as ecode: print("patch failed : {}".format(ecode))
def test_patch_conflict(self, co3_args, overwrite_conflict): """ do incident patch that results in conflict """ client = self._connect(co3_args) inc = self._create_incident(client, {"name": "test"}) uri = "/incidents/%d" % inc['id'] # Create a conflict inc["name"] = "the wrong value" inc["vers"] -= 1 # Force it to check old_value patch = resilient.Patch(inc) patch.add_value("name", "test updated") if overwrite_conflict: # If overwrite_conflict is specified then patch will return. response = client.patch(uri, patch, overwrite_conflict=overwrite_conflict) assert resilient.PatchStatus( response.json()).is_success() is overwrite_conflict else: # Not overwriting conflict, so an exception will be thrown. with pytest.raises( resilient.PatchConflictException) as exception_info: client.patch(uri, patch, overwrite_conflict=overwrite_conflict) # Gather the patch_status value from the exception for additional verification. patch_status = exception_info.value.patch_status fail_msg = "could not be applied due to a conflicting edit by another user. The following field(s) were in conflict: name." assert fail_msg in patch_status.get_message() assert patch_status.get_conflict_fields() == ["name"] assert patch_status.get_your_original_value( "name") == "the wrong value" assert patch_status.get_actual_current_value("name") == "test" inc = client.get("/incidents/%d" % inc['id']) if overwrite_conflict: assert inc['name'] == "test updated" else: assert inc['name'] == "test"
def test_no_old_value(self): patch = resilient.Patch({}) # this one is allowed patch.add_value("a", new_value=5, old_value=3) dto = patch.to_dict() changes = dto["changes"] assert len(changes) == 1 assert changes[0]["field"] == "a" assert changes[0]["old_value"]["object"] == 3 assert changes[0]["new_value"]["object"] == 5 with pytest.raises(ValueError) as exception_info: patch.add_value("b", new_value=10) assert "Constructor previous_object or method old_value argument is required" in str( exception_info.value)
def update_incident_properties(self, incident_id, fields): """ Update Resilient incident custom property or fields. :param incident_id: Incident ID. :param fields: Property fields to be updates. :return: response object """ try: response = None resilient_client = self.rest_client() uri = "/incidents/{}".format(incident_id) previous_object = resilient_client.get(uri) patch = resilient.Patch(previous_object) properties = fields.get("properties") if properties: for field in properties: if field not in previous_object["properties"]: raise ValueError( "Invalid property parameter {}".format(field)) patch.changes[field] = \ resilient.patch.Change(field, properties[field], previous_object["properties"][field]) else: for field in fields: patch.add_value(field, fields[field]) response = resilient_client.patch(uri, patch) except SimpleHTTPException as ex: LOG.error( 'Something went wrong when attempting to patch the Incident: %s', ex) return response
def main(): """ program main """ parser = ExampleArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() inc_id = opts["incid"] itypes = opts["itype"] # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) try: uri = '/incidents/{}?handle_format=names'.format(inc_id) incident = client.get(uri) # Create a patch object. You need to pass it the base object (the thing being patched). patch = resilient.Patch(incident) # The initial patch will contain the change we want to make. old_itypes = incident["incident_type_ids"] patch.add_value("incident_type_ids", old_itypes + itypes) def patch_conflict_handler(response, patch_status, patch): # If this gets called then there was a patch conflict, we we need to # adjust the patch to include an update. This only gets called if # a field we're trying to change has failed. In that case the actual # value currently on the server is included in the patch_status object. # # You can retrieve the current server value using # patch_status.get_actual_current_value(field_name). This # will return the actual value that exists on the server. # # In our case, we'll be appending to this value. # print("patch conflict detected, operation returned: ") print(json.dumps(patch_status.to_dict(), indent=4)) current_value = patch_status.get_actual_current_value( "incident_type_ids") patch.exchange_conflicting_value(patch_status, "incident_type_ids", current_value + itypes) print("existing itypes: {}".format(old_itypes)) print("wanted to add these: {}".format(itypes)) print(''' At this point, we have a copy of the specified incident. If you want to trigger a conflict to see what will happen, then you can do so now. Press the Enter key to continue''') input() client.patch_with_callback(uri, patch, patch_conflict_handler) # Confirm that our change was applied. This is not something that you'd typically need to do since the # patch applied successfully, but this illustrates that the change was applied for the purposes of this # example. # new_itypes = client.get(uri)["incident_type_ids"] # Remove the original description, which will leave only our addition. print("itypes after update: {}".format(new_itypes)) assert set(itypes).issubset(new_itypes) except resilient.SimpleHTTPException as ecode: print("patch failed : {}".format(ecode))