def test_optional_object_var(self): """ Test that an optional Object variable field behaves as expected. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_object_var_optional" name = "TestOptionalObjectVar" job_class = get_job(f"local/{module}/{name}") # Prepare the job data job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) data = {"region": None} # Run the job without the optional var provided run_job(data=data, request=self.request, commit=True, job_result_pk=job_result.pk) job_result.refresh_from_db() info_log = JobLogEntry.objects.filter( job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run" ).first() # Assert stuff self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED) self.assertEqual(info_log.log_object, None) self.assertEqual(info_log.message, "The Region if any that the user provided.") self.assertEqual(job_result.data["output"], "\nNice Region (or not)!")
def test_job_pass_with_run_job_directly(self): """ Job test with pass result calling run_job directly in order to test for backwards stability of its API. Because calling run_job directly used to be the best practice for testing jobs, we want to ensure that calling it still works even if we ever change the run_job call in the run_job_for_testing wrapper. """ module = "test_pass" name = "TestPass" job_class, job_model = get_job_class_and_model(module, name) job_model.enabled = True job_model.validated_save() job_content_type = ContentType.objects.get(app_label="extras", model="job") job_result = JobResult.objects.create( name=job_model.class_path, obj_type=job_content_type, job_model=job_model, user=None, job_id=uuid.uuid4(), ) run_job(data={}, request=None, commit=False, job_result_pk=job_result.pk) job_result = create_job_result_and_run_job(module, name, commit=False) self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED)
def test_job_fail(self): """ Job test with fail result. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_fail" name = "TestFail" job_class = get_job(f"local/{module}/{name}") job_content_type = ContentType.objects.get(app_label="extras", model="job") job_result = JobResult.objects.create( name=job_class.class_path, obj_type=job_content_type, user=None, job_id=uuid.uuid4(), ) run_job(data={}, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_ERRORED)
def test_ready_only_job_pass(self): """ Job read only test with pass result. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_read_only_pass" name = "TestReadOnlyPass" job_class = get_job(f"local/{module}/{name}") job_content_type = ContentType.objects.get(app_label="extras", model="job") job_result = JobResult.objects.create( name=job_class.class_path, obj_type=job_content_type, user=None, job_id=uuid.uuid4(), ) run_job(data={}, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED) self.assertEqual(Site.objects.count(), 0) # Ensure DB transaction was aborted
def test_read_only_job_fail(self): """ Job read only test with fail result. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_read_only_fail" name = "TestReadOnlyFail" job_class = get_job(f"local/{module}/{name}") job_content_type = ContentType.objects.get(app_label="extras", model="job") job_result = JobResult.objects.create( name=job_class.class_path, obj_type=job_content_type, user=None, job_id=uuid.uuid4(), ) run_job(data={}, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_ERRORED) self.assertEqual(Site.objects.count(), 0) # Ensure DB transaction was aborted # Also ensure the standard log message about aborting the transaction is *not* present self.assertNotEqual( job_result.data["run"]["log"][-1][-1], "Database changes have been reverted due to error.")
def test_run_job_fail(self): """Test that file upload succeeds; job FAILS; files deleted.""" with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): job_name = "local/test_file_upload_fail/TestFileUploadFail" job_class = get_job(job_name) job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) # Serialize the file to FileProxy data = {"file": self.dummy_file} form = job_class().as_form(files=data) self.assertTrue(form.is_valid()) serialized_data = job_class.serialize_data(form.cleaned_data) # Assert that the file was serialized to a FileProxy self.assertTrue(isinstance(serialized_data["file"], uuid.UUID)) self.assertEqual(serialized_data["file"], FileProxy.objects.latest().pk) self.assertEqual(FileProxy.objects.count(), 1) # Run the job run_job(data=serialized_data, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() # Can't check log objects when jobs are reverted (within tests anyways.) # This is due to the fake job_logs db not being available for tests. # Assert that FileProxy was cleaned up self.assertEqual(FileProxy.objects.count(), 0)
def test_run(self): with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): self.clear_worker() job_content_type = ContentType.objects.get(app_label="extras", model="job") job_name = "local/test_site_with_custom_field/TestCreateSiteWithCustomField" job_class = get_job(job_name) job_result = JobResult.objects.create( name=job_class.class_path, obj_type=job_content_type, user=None, job_id=uuid.uuid4(), ) # Run the job run_job(data={}, request=self.request, commit=True, job_result_pk=job_result.pk) self.wait_on_active_tasks() job_result.refresh_from_db() self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED) # Test site with a value for custom_field site_1 = Site.objects.filter(slug="test-site-one") self.assertEqual(site_1.count(), 1) self.assertEqual(CustomField.objects.filter(name="cf1").count(), 1) self.assertEqual(site_1[0].cf["cf1"], "some-value") # Test site with default value for custom field site_2 = Site.objects.filter(slug="test-site-two") self.assertEqual(site_2.count(), 1) self.assertEqual(site_2[0].cf["cf1"], "-")
def test_required_object_var(self): """ Test that a required Object variable field behaves as expected. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_object_var_required" name = "TestRequiredObjectVar" job_class = get_job(f"local/{module}/{name}") # Prepare the job data job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) data = {"region": None} run_job(data=data, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() # Assert stuff self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_ERRORED) log_failure = JobLogEntry.objects.filter( grouping="initialization", log_level=LogLevelChoices.LOG_FAILURE ).first() self.assertIn("region is a required field", log_failure.message)
def test_job_data_as_string(self): """ Test that job doesn't error when not a dictionary. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_object_vars" name = "TestObjectVars" job_class = get_job(f"local/{module}/{name}") # Prepare the job data job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) data = "BAD DATA STRING" run_job(data=data, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() # Assert stuff self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_ERRORED) log_failure = JobLogEntry.objects.filter( grouping="initialization", log_level=LogLevelChoices.LOG_FAILURE ).first() self.assertIn("Data should be a dictionary", log_failure.message)
def test_ip_address_vars(self): """ Test that IPAddress variable fields behave as expected. This test case exercises the following types for both IPv4 and IPv6: - IPAddressVar - IPAddressWithMaskVar - IPNetworkVar """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_ipaddress_vars" name = "TestIPAddresses" job_class = get_job(f"local/{module}/{name}") # Fill out the form form_data = dict( ipv4_address="1.2.3.4", ipv4_with_mask="1.2.3.4/32", ipv4_network="1.2.3.0/24", ipv6_address="2001:db8::1", ipv6_with_mask="2001:db8::1/64", ipv6_network="2001:db8::/64", ) form = job_class().as_form(form_data) self.assertTrue(form.is_valid()) # Prepare the job data job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) data = job_class.serialize_data(form.cleaned_data) # Run the job and extract the job payload data # Changing commit=True as commit=False will rollback database changes including the # logs that we are trying to read. See above note on why we are using the default database. # Also need to pass a mock request object as execute_webhooks will be called with the creation # of the objects. run_job(data=data, request=self.request, commit=True, job_result_pk=job_result.pk) job_result.refresh_from_db() log_info = JobLogEntry.objects.filter( job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run").first() job_result_data = json.loads(log_info.log_object) # Assert stuff self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED) self.assertEqual(form_data, job_result_data)
def run_job_for_testing(job, data=None, commit=True, username="******", request=None): """Provide a common interface to run Nautobot jobs as part of unit tests. Args: job (Job): Job model instance (not Job class) to run data (dict): Input data values for any Job variables. commit (bool): Whether to commit changes to the database or rollback when done. username (str): Username of existing or to-be-created User account to own the JobResult. Ignored if `request.user` exists. request (HttpRequest): Existing request (if any) to own the JobResult. Returns: JobResult: representing the executed job """ if data is None: data = {} # If the request has a user, ignore the username argument and use that user. if request and request.user: user_instance = request.user else: User = get_user_model() user_instance, _ = User.objects.get_or_create(username=username, defaults={ "is_superuser": True, "password": "******" }) job_result = JobResult.objects.create( name=job.class_path, obj_type=get_job_content_type(), user=user_instance, job_model=job, job_id=uuid.uuid4(), ) @contextmanager def _web_request_context(user): if request: yield request else: yield web_request_context(user=user) with _web_request_context(user=user_instance) as request: run_job(data=data, request=request, commit=commit, job_result_pk=job_result.pk) return job_result
def test_ip_address_vars(self): """ Test that IPAddress variable fields behave as expected. This test case exercises the following types for both IPv4 and IPv6: - IPAddressVar - IPAddressWithMaskVar - IPNetworkVar """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_ipaddress_vars" name = "TestIPAddresses" job_class = get_job(f"local/{module}/{name}") # Fill out the form form_data = dict( ipv4_address="1.2.3.4", ipv4_with_mask="1.2.3.4/32", ipv4_network="1.2.3.0/24", ipv6_address="2001:db8::1", ipv6_with_mask="2001:db8::1/64", ipv6_network="2001:db8::/64", ) form = job_class().as_form(form_data) self.assertTrue(form.is_valid()) # Prepare the job data job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) data = job_class.serialize_data(form.cleaned_data) # Run the job and extract the job payload data run_job(data=data, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() job_payload = job_result.data["run"]["log"][0][ 2] # Indexing makes me sad. job_result_data = json.loads(job_payload) # Assert stuff self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED) self.assertEqual(form_data, job_result_data)
def test_object_vars(self): """ Test that Object variable fields behave as expected. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_object_vars" name = "TestObjectVars" job_class = get_job(f"local/{module}/{name}") d = DeviceRole.objects.create(name="role", slug="role") # Prepare the job data job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) data = { "role": { "name": "role" }, "roles": [d.pk], } # Run the job and extract the job payload data # See test_ip_address_vars as to why we are changing commit=True and request=self.request. run_job(data=data, request=self.request, commit=True, job_result_pk=job_result.pk) job_result.refresh_from_db() # Test storing additional data in job job_result_data = job_result.data["object_vars"] info_log = JobLogEntry.objects.filter( job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run").first() # Assert stuff self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED) self.assertEqual({ "role": str(d.pk), "roles": [str(d.pk)] }, job_result_data) self.assertEqual(info_log.log_object, "Role: role") self.assertEqual(job_result.data["output"], "\nNice Roles, bro.")
def test_run_job_pass(self): """Test that file upload succeeds; job SUCCEEDS; and files are deleted.""" with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): job_name = "local/test_file_upload_pass/TestFileUploadPass" job_class = get_job(job_name) job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) # Serialize the file to FileProxy data = {"file": self.dummy_file} form = job_class().as_form(files=data) self.assertTrue(form.is_valid()) serialized_data = job_class.serialize_data(form.cleaned_data) # Assert that the file was serialized to a FileProxy self.assertTrue(isinstance(serialized_data["file"], uuid.UUID)) self.assertEqual(serialized_data["file"], FileProxy.objects.latest().pk) self.assertEqual(FileProxy.objects.count(), 1) # Run the job # See test_ip_address_vars as to why we are changing commit=True and request=self.request. run_job(data=serialized_data, request=self.request, commit=True, job_result_pk=job_result.pk) job_result.refresh_from_db() warning_log = JobLogEntry.objects.filter( job_result=job_result, log_level=LogLevelChoices.LOG_WARNING, grouping="run").first() # Assert that file contents were correctly read self.assertEqual( warning_log.message, f"File contents: {self.file_contents}") # "File contents: ..." # Assert that FileProxy was cleaned up self.assertEqual(FileProxy.objects.count(), 0)
def test_run_job_fail(self): """Test that file upload succeeds; job FAILS; files deleted.""" with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): job_name = "local/test_file_upload_fail/TestFileUploadFail" job_class = get_job(job_name) job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) # Serialize the file to FileProxy data = {"file": self.dummy_file} form = job_class().as_form(files=data) self.assertTrue(form.is_valid()) serialized_data = job_class.serialize_data(form.cleaned_data) # Assert that the file was serialized to a FileProxy self.assertTrue(isinstance(serialized_data["file"], uuid.UUID)) self.assertEqual(serialized_data["file"], FileProxy.objects.latest().pk) self.assertEqual(FileProxy.objects.count(), 1) # Run the job run_job(data=serialized_data, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() # Assert that file contents were correctly read self.assertEqual( job_result.data["run"]["log"][0][2], f"File contents: {self.file_contents}" # "File contents: ..." ) # Also ensure the standard log message about aborting the transaction is present self.assertEqual( job_result.data["run"]["log"][-1][-1], "Database changes have been reverted due to error.") # Assert that FileProxy was cleaned up self.assertEqual(FileProxy.objects.count(), 0)
def test_object_vars(self): """ Test that Object variable fields behave as expected. """ with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): module = "test_object_vars" name = "TestObjectVars" job_class = get_job(f"local/{module}/{name}") d = DeviceRole.objects.create(name="role", slug="role") # Prepare the job data job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) data = { "role": { "name": "role" }, "roles": [d.pk], } # Run the job and extract the job payload data run_job(data=data, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() job_payload = job_result.data["run"]["log"][0][ 2] # Indexing makes me sad. job_result_data = json.loads(job_payload) # Assert stuff self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_COMPLETED) self.assertEqual({ "role": str(d.pk), "roles": [str(d.pk)] }, job_result_data)
def test_run_job_pass(self): """Test that file upload succeeds; job SUCCEEDS; and files are deleted.""" with self.settings(JOBS_ROOT=os.path.join(settings.BASE_DIR, "extras/tests/dummy_jobs")): job_name = "local/test_file_upload_pass/TestFileUploadPass" job_class = get_job(job_name) job_result = JobResult.objects.create( name=job_class.class_path, obj_type=self.job_content_type, user=None, job_id=uuid.uuid4(), ) # Serialize the file to FileProxy data = {"file": self.dummy_file} form = job_class().as_form(files=data) self.assertTrue(form.is_valid()) serialized_data = job_class.serialize_data(form.cleaned_data) # Assert that the file was serialized to a FileProxy self.assertTrue(isinstance(serialized_data["file"], uuid.UUID)) self.assertEqual(serialized_data["file"], FileProxy.objects.latest().pk) self.assertEqual(FileProxy.objects.count(), 1) # Run the job run_job(data=serialized_data, request=None, commit=False, job_result_pk=job_result.pk) job_result.refresh_from_db() # Assert that file contents were correctly read self.assertEqual( job_result.data["run"]["log"][0][2], f"File contents: {self.file_contents}" # "File contents: ..." ) # Assert that FileProxy was cleaned up self.assertEqual(FileProxy.objects.count(), 0)