def test_delete_object(self): site = Site( name="Test Site 1", slug="test-site-1", custom_field_data={"my_field": "ABC", "my_field_select": "Bar"}, ) site.save() self.create_tags("Tag 1", "Tag 2") site.tags.set("Tag 1", "Tag 2") request = { "path": self._get_url("delete", instance=site), "data": post_data({"confirm": True}), } self.add_permissions("dcim.delete_site") response = self.client.post(**request) self.assertHttpStatus(response, 302) oc = ObjectChange.objects.first() self.assertEqual(oc.changed_object, None) self.assertEqual(oc.object_repr, site.name) self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_DELETE) self.assertEqual(oc.object_data["custom_fields"]["my_field"], "ABC") self.assertEqual(oc.object_data["custom_fields"]["my_field_select"], "Bar") self.assertEqual(oc.object_data["tags"], ["Tag 1", "Tag 2"])
def test_delete_object(self): site = Site( name="Test Site 1", slug="test-site-1", status=self.statuses.get(slug="active"), _custom_field_data={ "my_field": "ABC", "my_field_select": "Bar" }, ) site.save() site.tags.set(*Tag.objects.all()[:2]) self.assertEqual(ObjectChange.objects.count(), 0) self.add_permissions("dcim.delete_site", "extras.view_status") url = reverse("dcim-api:site-detail", kwargs={"pk": site.pk}) response = self.client.delete(url, **self.header) self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertEqual(Site.objects.count(), 0) oc = ObjectChange.objects.first() self.assertEqual(oc.changed_object, None) self.assertEqual(oc.object_repr, site.name) self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_DELETE) self.assertEqual(oc.object_data["custom_fields"]["my_field"], "ABC") self.assertEqual(oc.object_data["custom_fields"]["my_field_select"], "Bar") self.assertEqual(oc.object_data["tags"], ["Tag 1", "Tag 2"])
def test_update_object(self): site = Site( name="Test Site 1", slug="test-site-1", status=self.statuses.get(slug="planned"), ) site.save() data = { "name": "Test Site X", "slug": "test-site-x", "status": "active", "custom_fields": { "my_field": "DEF", "my_field_select": "Foo", }, "tags": [{"name": "Tag 3"}], } self.assertEqual(ObjectChange.objects.count(), 0) self.add_permissions("dcim.change_site", "extras.view_status") url = reverse("dcim-api:site-detail", kwargs={"pk": site.pk}) response = self.client.put(url, data, format="json", **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) site = Site.objects.get(pk=response.data["id"]) # Get only the most recent OC oc = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model(Site), changed_object_id=site.pk, ).first() self.assertEqual(oc.changed_object, site) self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_UPDATE) self.assertEqual(oc.object_data["custom_fields"], data["custom_fields"]) self.assertEqual(oc.object_data["tags"], ["Tag 3"])
def setUpTestData(cls): site = Site(name="Site 1", slug="site-1") site.save() # Create three ObjectChanges user = User.objects.create_user(username="******") for i in range(1, 4): oc = site.to_objectchange(action=ObjectChangeActionChoices.ACTION_UPDATE) oc.user = user oc.request_id = uuid.uuid4() oc.save()
def test_change_webhook_enqueued(self): with web_request_context(self.user): site = Site(name="Test Site 2") site.save() # Verify that a job was queued for the object creation webhook site = Site.objects.get(name="Test Site 2") self.assertEqual(self.queue.count, 1) job = self.queue.jobs[0] self.assertEqual(job.args[0], Webhook.objects.get(type_create=True)) self.assertEqual(job.args[1]["id"], str(site.pk)) self.assertEqual(job.args[2], "site")
def test_change_log_created(self): with web_request_context(self.user): site = Site(name="Test Site 1") site.save() site = Site.objects.get(name="Test Site 1") oc_list = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model(Site), changed_object_id=site.pk, ).order_by("pk") self.assertEqual(len(oc_list), 1) self.assertEqual(oc_list[0].changed_object, site) self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_CREATE)
def test_cf_data(self): """ Check that custom field data is present on the instance immediately after being set and after being fetched from the database. """ site = Site(name="Test Site", slug="test-site") # Check custom field data on new instance site.cf["foo"] = "abc" self.assertEqual(site.cf["foo"], "abc") # Check custom field data from database site.save() site = Site.objects.get(name="Test Site") self.assertEqual(site.cf["foo"], "abc")
def test_view_object_with_custom_link(self): customlink = CustomLink( content_type=ContentType.objects.get_for_model(Site), name="Test", text="FOO {{ obj.name }} BAR", target_url="http://example.com/?site={{ obj.slug }}", new_window=False, ) customlink.save() site = Site(name="Test Site", slug="test-site") site.save() response = self.client.get(site.get_absolute_url(), follow=True) self.assertEqual(response.status_code, 200) self.assertIn(f"FOO {site.name} BAR", str(response.content))
def test_webhooks_snapshot_on_create(self): request_id = uuid.uuid4() webhook = Webhook.objects.get(type_create=True) timestamp = str(timezone.now()) def mock_send(_, request, **kwargs): # Validate the outgoing request body body = json.loads(request.body) self.assertEqual(body["data"]["name"], "Site 1") self.assertEqual(body["snapshots"]["prechange"], None) self.assertEqual(body["snapshots"]["postchange"]["name"], "Site 1") self.assertEqual(body["snapshots"]["differences"]["removed"], None) self.assertEqual(body["snapshots"]["differences"]["added"]["name"], "Site 1") class FakeResponse: ok = True status_code = 200 return FakeResponse() # Patch the Session object with our mock_send() method, then process the webhook for sending with patch.object(Session, "send", mock_send): request = mock.MagicMock() request.user = self.user request.id = request_id with change_logging(request): site = Site(name="Site 1", slug="site-1") site.save() serializer = SiteSerializer(site, context={"request": None}) snapshots = get_snapshots( site, ObjectChangeActionChoices.ACTION_CREATE) process_webhook( webhook.pk, serializer.data, Site._meta.model_name, ObjectChangeActionChoices.ACTION_CREATE, timestamp, self.user.username, request_id, snapshots, )
def setUp(self): obj_type = ContentType.objects.get_for_model(Site) self.cf = CustomField( name="cf1", type=CustomFieldTypeChoices.TYPE_SELECT, ) self.cf.save() self.cf.content_types.set([obj_type]) self.choice = CustomFieldChoice(field=self.cf, value="Foo") self.choice.save() self.site = Site( name="Site 1", slug="site-1", _custom_field_data={ "cf1": "Foo", }, ) self.site.save()
class CustomFieldChoiceTest(TestCase): def setUp(self): obj_type = ContentType.objects.get_for_model(Site) self.cf = CustomField( name="cf1", type=CustomFieldTypeChoices.TYPE_SELECT, ) self.cf.save() self.cf.content_types.set([obj_type]) self.choice = CustomFieldChoice(field=self.cf, value="Foo") self.choice.save() self.site = Site( name="Site 1", slug="site-1", _custom_field_data={ "cf1": "Foo", }, ) self.site.save() def test_default_value_must_be_valid_choice_sad_path(self): self.cf.default = "invalid value" with self.assertRaises(ValidationError): self.cf.full_clean() def test_default_value_must_be_valid_choice_happy_path(self): self.cf.default = "Foo" self.cf.full_clean() self.cf.save() self.assertEqual(self.cf.default, "Foo") def test_active_choice_cannot_be_deleted(self): with self.assertRaises(ProtectedError): self.choice.delete() def test_custom_choice_deleted_with_field(self): self.cf.delete() self.assertEqual(CustomField.objects.count(), 0) self.assertEqual(CustomFieldChoice.objects.count(), 0)
def test_update_custom_field_choice_data_task(self): self.clear_worker() obj_type = ContentType.objects.get_for_model(Site) cf = CustomField( name="cf1", type=CustomFieldTypeChoices.TYPE_SELECT, ) cf.save() cf.content_types.set([obj_type]) self.wait_on_active_tasks() choice = CustomFieldChoice(field=cf, value="Foo") choice.save() site = Site(name="Site 1", slug="site-1", _custom_field_data={"cf1": "Foo"}) site.save() choice.value = "Bar" choice.save() self.wait_on_active_tasks() site.refresh_from_db() self.assertEqual(site.cf["cf1"], "Bar")
def test_delete_custom_field_data_task(self): obj_type = ContentType.objects.get_for_model(Site) cf = CustomField( name="cf1", type=CustomFieldTypeChoices.TYPE_TEXT, ) cf.save() cf.content_types.set([obj_type]) # Synchronously process all jobs on the queue in this process self.get_worker() site = Site(name="Site 1", slug="site-1", _custom_field_data={"cf1": "foo"}) site.save() cf.delete() # Synchronously process all jobs on the queue in this process self.get_worker() site.refresh_from_db() self.assertTrue("cf1" not in site.cf)
def test_invalid_data(self): """ Setting custom field data for a non-applicable (or non-existent) CustomField should raise a ValidationError. """ site = Site(name="Test Site", slug="test-site") # Set custom field data site.cf["foo"] = "abc" site.cf["bar"] = "def" with self.assertRaises(ValidationError): site.clean() del site.cf["bar"] site.clean()
def test_update_custom_field_choice_data_task(self): obj_type = ContentType.objects.get_for_model(Site) cf = CustomField( name="cf1", type=CustomFieldTypeChoices.TYPE_SELECT, ) cf.save() cf.content_types.set([obj_type]) # Synchronously process all jobs on the queue in this process self.get_worker() choice = CustomFieldChoice(field=cf, value="Foo") choice.save() site = Site(name="Site 1", slug="site-1", _custom_field_data={"cf1": "Foo"}) site.save() choice.value = "Bar" choice.save() # Synchronously process all jobs on the queue in this process self.get_worker() site.refresh_from_db() self.assertEqual(site.cf["cf1"], "Bar")
def test_missing_required_field(self): """ Check that a ValidationError is raised if any required custom fields are not present. """ cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name="baz", required=True) cf3.save() cf3.content_types.set([ContentType.objects.get_for_model(Site)]) site = Site(name="Test Site", slug="test-site") # Set custom field data with a required field omitted site.cf["foo"] = "abc" with self.assertRaises(ValidationError): site.clean() site.cf["baz"] = "def" site.clean()
def evaluate_ordering(self, names): # Create the Sites Site.objects.bulk_create( Site(name=name, slug=name.lower()) for name in names) # Validate forward ordering self.assertEqual(names, list(Site.objects.values_list("name", flat=True))) # Validate reverse ordering self.assertEqual( list(reversed(names)), list(Site.objects.reverse().values_list("name", flat=True)), )
def setUpTestData(cls): obj_type = ContentType.objects.get_for_model(Site) computed_fields = ( ComputedField( content_type=obj_type, label="Computed Field One", slug="computed_field_one", template="Site name is {{ obj.name }}", fallback_value="Template error", weight=100, ), ComputedField( content_type=obj_type, slug="computed_field_two", label="Computed Field Two", template="Site name is {{ obj.name }}", fallback_value="Template error", weight=100, ), ComputedField( content_type=obj_type, slug="computed_field_three", label="Computed Field Three", template="Site name is {{ obj.name }}", fallback_value="Template error", weight=100, ), ) cls.site1 = Site(name="NYC") cls.site1.save() for cf in computed_fields: cf.save() cls.form_data = { "content_type": obj_type.pk, "slug": "computed_field_four", "label": "Computed Field Four", "template": "{{ obj.name }} is the best Site!", "fallback_value": ":skull_emoji:", "weight": 100, }
def test_provision_field_task(self): self.clear_worker() site = Site( name="Site 1", slug="site-1", ) site.save() obj_type = ContentType.objects.get_for_model(Site) cf = CustomField(name="cf1", type=CustomFieldTypeChoices.TYPE_TEXT, default="Foo") cf.save() cf.content_types.set([obj_type]) self.wait_on_active_tasks() site.refresh_from_db() self.assertEqual(site.cf["cf1"], "Foo")
def test_update_object(self): site = Site( name="Test Site 1", slug="test-site-1", status=Status.objects.get(slug="active"), ) site.save() tags = self.create_tags("Tag 1", "Tag 2", "Tag 3") site.tags.set("Tag 1", "Tag 2") form_data = { "name": "Test Site X", "slug": "test-site-x", "status": Status.objects.get(slug="planned").pk, "cf_my_field": "DEF", "cf_my_field_select": "Foo", "tags": [tags[2].pk], } request = { "path": self._get_url("edit", instance=site), "data": post_data(form_data), } self.add_permissions("dcim.change_site", "extras.view_tag", "extras.view_status") response = self.client.post(**request) self.assertHttpStatus(response, 302) # Verify the creation of a new ObjectChange record site.refresh_from_db() oc = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model(Site), changed_object_id=site.pk, ).first() self.assertEqual(oc.changed_object, site) self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_UPDATE) self.assertEqual(oc.object_data["custom_fields"]["my_field"], form_data["cf_my_field"]) self.assertEqual( oc.object_data["custom_fields"]["my_field_select"], form_data["cf_my_field_select"], ) self.assertEqual(oc.object_data["tags"], ["Tag 3"])
def test_provision_field_task(self): site = Site( name="Site 1", slug="site-1", ) site.save() obj_type = ContentType.objects.get_for_model(Site) cf = CustomField(name="cf1", type=CustomFieldTypeChoices.TYPE_TEXT, default="Foo") cf.save() cf.content_types.set([obj_type]) # Synchronously process all jobs on the queue in this process self.get_worker() site.refresh_from_db() self.assertEqual(site.cf["cf1"], "Foo")
def test_delete_custom_field_data_task(self): self.clear_worker() obj_type = ContentType.objects.get_for_model(Site) cf = CustomField( name="cf1", type=CustomFieldTypeChoices.TYPE_TEXT, ) cf.save() cf.content_types.set([obj_type]) site = Site(name="Site 1", slug="site-1", _custom_field_data={"cf1": "foo"}) site.save() cf.delete() self.wait_on_active_tasks() site.refresh_from_db() self.assertTrue("cf1" not in site.cf)
def setUpTestData(cls): manufacturers = ( Manufacturer(name="Manufacturer 1", slug="manufacturer-1"), Manufacturer(name="Manufacturer 2", slug="manufacturer-2"), Manufacturer(name="Manufacturer 3", slug="manufacturer-3"), ) Manufacturer.objects.bulk_create(manufacturers) device_types = ( DeviceType( manufacturer=manufacturers[0], model="Model 1", slug="model-1", is_full_depth=True, ), DeviceType( manufacturer=manufacturers[1], model="Model 2", slug="model-2", is_full_depth=True, ), DeviceType( manufacturer=manufacturers[2], model="Model 3", slug="model-3", is_full_depth=False, ), ) DeviceType.objects.bulk_create(device_types) device_roles = ( DeviceRole(name="Device Role 1", slug="device-role-1"), DeviceRole(name="Device Role 2", slug="device-role-2"), DeviceRole(name="Device Role 3", slug="device-role-3"), ) DeviceRole.objects.bulk_create(device_roles) device_statuses = Status.objects.get_for_model(Device) device_status_map = {ds.slug: ds for ds in device_statuses.all()} platforms = ( Platform(name="Platform 1", slug="platform-1"), Platform(name="Platform 2", slug="platform-2"), Platform(name="Platform 3", slug="platform-3"), ) Platform.objects.bulk_create(platforms) regions = ( Region(name="Region 1", slug="region-1"), Region(name="Region 2", slug="region-2"), Region(name="Region 3", slug="region-3"), ) for region in regions: region.save() sites = ( Site(name="Site 1", slug="abc-site-1", region=regions[0], asn=65001), Site(name="Site 2", slug="def-site-2", region=regions[1], asn=65101), Site(name="Site 3", slug="ghi-site-3", region=regions[2], asn=65201), ) Site.objects.bulk_create(sites) racks = ( Rack(name="Rack 1", site=sites[0]), Rack(name="Rack 2", site=sites[1]), Rack(name="Rack 3", site=sites[2]), ) Rack.objects.bulk_create(racks) devices = ( Device( name="Device 1", device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial="ABC", asset_tag="1001", site=sites[0], rack=racks[0], position=1, face=DeviceFaceChoices.FACE_FRONT, status=device_status_map["active"], local_context_data={"foo": 123}, comments="Device 1 comments", ), Device( name="Device 2", device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], serial="DEF", asset_tag="1002", site=sites[1], rack=racks[1], position=2, face=DeviceFaceChoices.FACE_FRONT, status=device_status_map["staged"], comments="Device 2 comments", ), Device( name="Device 3", device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], serial="GHI", asset_tag="1003", site=sites[2], rack=racks[2], position=3, face=DeviceFaceChoices.FACE_REAR, status=device_status_map["failed"], comments="Device 3 comments", ), ) Device.objects.bulk_create(devices) interfaces = ( Interface(device=devices[0], name="Interface 1", mac_address="00-00-00-00-00-01"), Interface(device=devices[0], name="Interface 2", mac_address="aa-00-00-00-00-01"), Interface(device=devices[1], name="Interface 3", mac_address="00-00-00-00-00-02"), Interface(device=devices[1], name="Interface 4", mac_address="bb-00-00-00-00-02"), Interface(device=devices[2], name="Interface 5", mac_address="00-00-00-00-00-03"), Interface(device=devices[2], name="Interface 6", mac_address="cc-00-00-00-00-03"), ) Interface.objects.bulk_create(interfaces)
def test_custom_validator_raises_exception(self): site = Site(name="this site has a matching name", slug="site1") with self.assertRaises(ValidationError): site.clean()
def test_webhooks_snapshot_without_model_api_serializer( self, get_serializer_for_model): def get_serializer(model_class): raise SerializerNotFound get_serializer_for_model.side_effect = get_serializer request_id = uuid.uuid4() webhook = Webhook.objects.get(type_create=True) timestamp = str(timezone.now()) def mock_send(_, request, **kwargs): # Validate the outgoing request body body = json.loads(request.body) self.assertEqual(body["snapshots"]["prechange"]["status"], str(self.active_status.id)) self.assertEqual(body["snapshots"]["prechange"]["region"], str(self.region_one.id)) self.assertEqual(body["snapshots"]["postchange"]["name"], "Site Update") self.assertEqual(body["snapshots"]["postchange"]["status"], str(self.planned_status.id)) self.assertEqual(body["snapshots"]["postchange"]["region"], str(self.region_two.id)) self.assertEqual( body["snapshots"]["differences"]["removed"]["name"], "Site 1") self.assertEqual(body["snapshots"]["differences"]["added"]["name"], "Site Update") class FakeResponse: ok = True status_code = 200 return FakeResponse() with patch.object(Session, "send", mock_send): self.client.force_login(self.user) request = mock.MagicMock() request.user = self.user request.id = request_id with change_logging(request): site = Site(name="Site 1", slug="site-1", status=self.active_status, region=self.region_one) site.save() site.name = "Site Update" site.status = self.planned_status site.region = self.region_two site.save() serializer = SiteSerializer(site, context={"request": None}) snapshots = get_snapshots( site, ObjectChangeActionChoices.ACTION_UPDATE) process_webhook( webhook.pk, serializer.data, Site._meta.model_name, ObjectChangeActionChoices.ACTION_CREATE, timestamp, self.user.username, request_id, snapshots, )
def test_webhooks_process_webhook_on_update(self): """ Mock a Session.send to inspect the result of `process_webhook()`. Note that process_webhook is called directly, not via a celery task. """ request_id = uuid.uuid4() webhook = Webhook.objects.get(type_create=True) timestamp = str(timezone.now()) def mock_send(_, request, **kwargs): """ A mock implementation of Session.send() to be used for testing. Always returns a 200 HTTP response. """ 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) self.assertEqual(request.headers["X-Foo"], "Bar") # Validate the outgoing request body body = json.loads(request.body) self.assertEqual(body["event"], "created") self.assertEqual(body["timestamp"], timestamp) self.assertEqual(body["model"], "site") self.assertEqual(body["username"], "testuser") self.assertEqual(body["request_id"], str(request_id)) self.assertEqual(body["data"]["name"], "Site Update") self.assertEqual(body["data"]["status"]["value"], self.planned_status.slug) self.assertEqual(body["data"]["region"]["slug"], self.region_two.slug) self.assertEqual(body["snapshots"]["prechange"]["name"], "Site 1") self.assertEqual(body["snapshots"]["prechange"]["status"]["value"], self.active_status.slug) self.assertEqual(body["snapshots"]["prechange"]["region"]["slug"], self.region_one.slug) self.assertEqual(body["snapshots"]["postchange"]["name"], "Site Update") self.assertEqual( body["snapshots"]["postchange"]["status"]["value"], self.planned_status.slug) self.assertEqual(body["snapshots"]["postchange"]["region"]["slug"], self.region_two.slug) self.assertEqual( body["snapshots"]["differences"]["removed"]["name"], "Site 1") self.assertEqual(body["snapshots"]["differences"]["added"]["name"], "Site Update") class FakeResponse: ok = True status_code = 200 return FakeResponse() # Patch the Session object with our mock_send() method, then process the webhook for sending with patch.object(Session, "send", mock_send): self.client.force_login(self.user) request = mock.MagicMock() request.user = self.user request.id = request_id with change_logging(request): site = Site(name="Site 1", slug="site-1", status=self.active_status, region=self.region_one) site.save() site.name = "Site Update" site.status = self.planned_status site.region = self.region_two site.save() serializer = SiteSerializer(site, context={"request": None}) snapshots = get_snapshots( site, ObjectChangeActionChoices.ACTION_UPDATE) process_webhook( webhook.pk, serializer.data, Site._meta.model_name, ObjectChangeActionChoices.ACTION_CREATE, timestamp, self.user.username, request_id, snapshots, )