def test_worker(self): request_id = uuid.uuid4() def mock_send(_, request, **kwargs): """ Mocked implementation of Session.send() that always returns a 200 HTTP status code. """ webhook = Webhook.objects.get(type_create=True) signature = generate_signature(request.body, webhook.secret) # Validate the outgoing request headers self.assertEqual(request.headers["Content-Type"], webhook.http_content_type) self.assertEqual(request.headers["X-Hook-Signature"], signature) # Validate the outgoing request body body = json.loads(request.body) self.assertEqual(body["event"], ObjectChangeAction.CREATE) self.assertEqual(body["timestamp"], job.args[4]) self.assertEqual(body["model"], "autonomoussystem") self.assertEqual(body["username"], "testuser") self.assertEqual(body["request_id"], str(request_id)) self.assertEqual(body["data"]["name"], "Guillaume Mazoyer") return HttpResponse() # Enqueue a webhook for processing a_s = AutonomousSystem.objects.create(asn=201281, name="Guillaume Mazoyer") enqueue_webhooks(a_s, self.user, request_id, ObjectChangeAction.CREATE) job = self.queue.jobs[0] with patch.object(Session, "send", mock_send): process_webhook(*job.args)
def __call__(self, request): # Prepare to collect objects that have been changed local_thread.changed_objects = [] # Assign an ID to the given request in case we have to handle multiple objects request.id = uuid.uuid4() # Listen for objects being saved (created/updated) and deleted post_save.connect(cache_changed_object, dispatch_uid="log_object_being_changed") pre_delete.connect( cache_deleted_object, dispatch_uid="log_object_being_deleted" ) # Process the request response = self.get_response(request) # Nothing to do as there are no changes to process if not local_thread.changed_objects: return response # Stop listening for object changes post_save.disconnect( cache_changed_object, dispatch_uid="log_object_being_changed" ) pre_delete.disconnect( cache_deleted_object, dispatch_uid="log_object_being_deleted" ) # Record change for each object that need to be tracked has_redis_failed = False for changed_object, action in local_thread.changed_objects: if hasattr(changed_object, "log_change"): changed_object.log_change(request.user, request.id, action) try: enqueue_webhooks(changed_object, request.user, request.id, action) except RedisError as e: if not has_redis_failed: messages.error( request, f"An error has occured while processing webhooks for this request. Check that the Redis service is running and reachable. The full error details were: {e}", ) has_redis_failed = True # Cleanup object changes that are too old (based on changelog retention) if local_thread.changed_objects and settings.CHANGELOG_RETENTION: date_limit = timezone.now() - timedelta(days=settings.CHANGELOG_RETENTION) ObjectChange.objects.filter(time__lt=date_limit).delete() return response
def _handle_deleted_object(request, sender, instance, **kwargs): """ Fires when an object is deleted. """ # Record an ObjectChange if applicable if hasattr(instance, "get_change"): change = instance.get_change(ObjectChangeAction.DELETE) change.user = request.user change.request_id = request.id change.save() # Enqueue webhooks enqueue_webhooks(instance, request.user, request.id, ObjectChangeAction.DELETE) # Increment metric counters model_deletes.labels(instance._meta.model_name).inc()
def _handle_changed_object(request, sender, instance, **kwargs): """ Fires when an object is created or updated. """ # Queue the object for processing once the request completes if kwargs.get("created"): action = ObjectChangeAction.CREATE elif "created" in kwargs: action = ObjectChangeAction.UPDATE elif kwargs.get("action") in ["post_add", "post_remove" ] and kwargs["pk_set"]: # m2m_changed with objects added or removed action = ObjectChangeAction.UPDATE else: return # Record an ObjectChange if applicable if hasattr(instance, "get_change"): change = instance.get_change(action) change.user = request.user change.request_id = request.id change.save() # Enqueue webhooks enqueue_webhooks(instance, request.user, request.id, action) # Increment metric counters if action == ObjectChangeAction.CREATE: model_inserts.labels(instance._meta.model_name).inc() elif action == ObjectChangeAction.UPDATE: model_updates.labels(instance._meta.model_name).inc() # Housekeeping: 0.1% chance of clearing out expired ObjectChanges if settings.CHANGELOG_RETENTION and random.randint(1, 1000) == 1: date_limit = timezone.now() - timedelta( days=settings.CHANGELOG_RETENTION) ObjectChange.objects.filter(time__lt=date_limit).delete()