Skip to content

Commit c717166

Browse files
committed
update tests
1 parent c19d82d commit c717166

File tree

1 file changed

+235
-56
lines changed

1 file changed

+235
-56
lines changed

netbox_branching/tests/test_merge.py

Lines changed: 235 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,164 @@ def _create_and_provision_branch(self, name='Test Branch'):
5050
branch.refresh_from_db() # Refresh to get updated status
5151
return branch
5252

53+
def test_merge_basic_create(self):
54+
"""
55+
Test basic create operation.
56+
Verifies object is created in main and ObjectChange was tracked.
57+
"""
58+
# Create branch
59+
branch = self._create_and_provision_branch()
60+
61+
# Create a request context for event tracking
62+
request = RequestFactory().get(reverse('home'))
63+
request.id = uuid.uuid4()
64+
request.user = self.user
65+
66+
# In branch: create site
67+
with activate_branch(branch), event_tracking(request):
68+
site = Site.objects.create(name='Test Site', slug='test-site')
69+
site_id = site.id
70+
71+
# Verify ObjectChange was created in branch
72+
from django.contrib.contenttypes.models import ContentType
73+
site_ct = ContentType.objects.get_for_model(Site)
74+
changes = branch.get_unmerged_changes().filter(
75+
changed_object_type=site_ct,
76+
changed_object_id=site_id
77+
)
78+
self.assertEqual(changes.count(), 1)
79+
self.assertEqual(changes.first().action, 'create')
80+
81+
# Merge branch
82+
branch.merge(user=self.user, commit=True)
83+
84+
# Verify site exists in main
85+
self.assertTrue(Site.objects.filter(id=site_id).exists())
86+
site = Site.objects.get(id=site_id)
87+
self.assertEqual(site.name, 'Test Site')
88+
self.assertEqual(site.slug, 'test-site')
89+
90+
def test_merge_basic_update(self):
91+
"""
92+
Test basic update operation.
93+
Verifies object is updated in main and ObjectChange was tracked.
94+
"""
95+
# Create site in main
96+
site = Site.objects.create(name='Original Site', slug='test-site', description='Original')
97+
site_id = site.id
98+
99+
# Create branch
100+
branch = self._create_and_provision_branch()
101+
102+
# Create a request context for event tracking
103+
request = RequestFactory().get(reverse('home'))
104+
request.id = uuid.uuid4()
105+
request.user = self.user
106+
107+
# In branch: update site
108+
with activate_branch(branch), event_tracking(request):
109+
site = Site.objects.get(id=site_id)
110+
site.description = 'Updated'
111+
site.save()
112+
113+
# Verify ObjectChange was created in branch
114+
from django.contrib.contenttypes.models import ContentType
115+
site_ct = ContentType.objects.get_for_model(Site)
116+
changes = branch.get_unmerged_changes().filter(
117+
changed_object_type=site_ct,
118+
changed_object_id=site_id
119+
)
120+
self.assertEqual(changes.count(), 1)
121+
self.assertEqual(changes.first().action, 'update')
122+
123+
# Merge branch
124+
branch.merge(user=self.user, commit=True)
125+
126+
# Verify site is updated in main
127+
site = Site.objects.get(id=site_id)
128+
self.assertEqual(site.description, 'Updated')
129+
130+
def test_merge_basic_delete(self):
131+
"""
132+
Test basic delete operation.
133+
Verifies object is deleted in main and ObjectChange was tracked.
134+
"""
135+
# Create site in main
136+
site = Site.objects.create(name='Test Site', slug='test-site')
137+
site_id = site.id
138+
139+
# Create branch
140+
branch = self._create_and_provision_branch()
141+
142+
# Create a request context for event tracking
143+
request = RequestFactory().get(reverse('home'))
144+
request.id = uuid.uuid4()
145+
request.user = self.user
146+
147+
# In branch: delete site
148+
with activate_branch(branch), event_tracking(request):
149+
Site.objects.get(id=site_id).delete()
150+
151+
# Verify ObjectChange was created in branch
152+
from django.contrib.contenttypes.models import ContentType
153+
site_ct = ContentType.objects.get_for_model(Site)
154+
changes = branch.get_unmerged_changes().filter(
155+
changed_object_type=site_ct,
156+
changed_object_id=site_id
157+
)
158+
self.assertEqual(changes.count(), 1)
159+
self.assertEqual(changes.first().action, 'delete')
160+
161+
# Merge branch
162+
branch.merge(user=self.user, commit=True)
163+
164+
# Verify site is deleted in main
165+
self.assertFalse(Site.objects.filter(id=site_id).exists())
166+
167+
def test_merge_basic_create_update_delete(self):
168+
"""
169+
Test create, update, then delete same object.
170+
Verifies object is skipped (not in main) after collapsing.
171+
"""
172+
# Create branch
173+
branch = self._create_and_provision_branch()
174+
175+
# Create a request context for event tracking
176+
request = RequestFactory().get(reverse('home'))
177+
request.id = uuid.uuid4()
178+
request.user = self.user
179+
180+
# In branch: create, update, then delete site
181+
with activate_branch(branch), event_tracking(request):
182+
site = Site.objects.create(name='Temp Site', slug='temp-site')
183+
site_id = site.id
184+
185+
site.description = 'Modified'
186+
site.save()
187+
188+
site.delete()
189+
190+
# Verify 3 ObjectChanges were created in branch
191+
from django.contrib.contenttypes.models import ContentType
192+
site_ct = ContentType.objects.get_for_model(Site)
193+
changes = branch.get_unmerged_changes().filter(
194+
changed_object_type=site_ct,
195+
changed_object_id=site_id
196+
)
197+
self.assertEqual(changes.count(), 3)
198+
actions = [c.action for c in changes.order_by('time')]
199+
self.assertEqual(actions, ['create', 'update', 'delete'])
200+
201+
# Merge branch
202+
branch.merge(user=self.user, commit=True)
203+
204+
# Verify site does not exist in main (skipped during merge)
205+
self.assertFalse(Site.objects.filter(id=site_id).exists())
206+
53207
def test_merge_delete_then_create_same_slug(self):
54208
"""
55-
Test merging when a site is deleted and a new site with the same slug is created.
56-
This was the original bug: deletes must happen before creates to free up unique constraints.
209+
Test delete object, then create object with same unique constraint value (slug).
210+
Verifies deletes free up unique constraints before creates.
57211
"""
58212
# Create site in main
59213
site1 = Site.objects.create(name='Site 1', slug='site-1')
@@ -94,8 +248,8 @@ def test_merge_delete_then_create_same_slug(self):
94248

95249
def test_merge_create_device_and_delete_old(self):
96250
"""
97-
Test merging when a new device is created and an old device is deleted.
98-
Tests ordering with dependencies.
251+
Test create new object, then delete old object.
252+
Verifies proper ordering with cascade delete dependencies.
99253
"""
100254
# Create device with interface in main
101255
site = Site.objects.create(name='Site 1', slug='site-1')
@@ -152,38 +306,10 @@ def test_merge_create_device_and_delete_old(self):
152306
self.assertTrue(Device.objects.filter(id=device_b_id).exists())
153307
self.assertTrue(Interface.objects.filter(id=interface_b_id).exists())
154308

155-
def test_merge_create_and_delete_same_object(self):
156-
"""
157-
Test that creating and deleting the same object in a branch results in no change (skip).
158-
"""
159-
# Create branch
160-
branch = self._create_and_provision_branch()
161-
162-
# Create a request context for event tracking
163-
request = RequestFactory().get(reverse('home'))
164-
request.id = uuid.uuid4() # Set request id for ObjectChange tracking
165-
request.user = self.user
166-
167-
# In branch: create and delete a site
168-
with activate_branch(branch), event_tracking(request):
169-
site = Site.objects.create(name='Temp Site', slug='temp-site')
170-
site_id = site.id
171-
site.delete()
172-
173-
# Merge branch
174-
branch.merge(user=self.user, commit=True)
175-
176-
# Verify main schema - site should not exist
177-
self.assertFalse(Site.objects.filter(id=site_id).exists())
178-
179-
# Verify merge succeeded
180-
branch.refresh_from_db()
181-
self.assertEqual(branch.status, BranchStatusChoices.MERGED)
182-
183309
def test_merge_slug_rename_then_create(self):
184310
"""
185-
Test merging when a site's slug is changed, then a new site is created with the old slug.
186-
Updates should happen before creates to free up the slug.
311+
Test update object's unique field, then create new object with old value.
312+
Verifies updates free up unique constraints before creates.
187313
"""
188314
# Create site in main
189315
site1 = Site.objects.create(name='Site 1', slug='site-1')
@@ -220,7 +346,8 @@ def test_merge_slug_rename_then_create(self):
220346

221347
def test_merge_multiple_updates_collapsed(self):
222348
"""
223-
Test that multiple updates to the same object are collapsed into a single update.
349+
Test multiple updates to same object.
350+
Verifies consecutive non-referencing updates are collapsed.
224351
"""
225352
# Create site in main
226353
site = Site.objects.create(name='Site 1', slug='site-1', description='Original')
@@ -257,8 +384,8 @@ def test_merge_multiple_updates_collapsed(self):
257384

258385
def test_merge_create_with_multiple_updates(self):
259386
"""
260-
Test that creating an object and then updating it multiple times
261-
results in a single create with the final state.
387+
Test create object then update it multiple times.
388+
Verifies create is kept separate from updates.
262389
"""
263390
# Create branch
264391
branch = self._create_and_provision_branch()
@@ -291,7 +418,8 @@ def test_merge_create_with_multiple_updates(self):
291418

292419
def test_merge_complex_dependency_chain(self):
293420
"""
294-
Test a complex scenario with creates, updates, and deletes with dependencies.
421+
Test complex scenario with multiple creates, updates, and deletes.
422+
Verifies correct ordering with FK dependencies and references.
295423
"""
296424
# Create initial devices in main
297425
site = Site.objects.create(name='Site 1', slug='site-1')
@@ -364,39 +492,90 @@ def test_merge_complex_dependency_chain(self):
364492
self.assertEqual(device_b.name, 'Device B Updated')
365493
self.assertEqual(device_b.interfaces.count(), 2)
366494

367-
def test_merge_delete_ordering_by_time(self):
495+
def test_merge_conflicting_slug_create_update_delete(self):
368496
"""
369-
Test that deletes maintain time order when there are no dependencies.
497+
Test create object with conflicting unique constraint, update it, then delete it.
498+
Verifies skipped object doesn't cause constraint violations.
370499
"""
371-
# Create sites in main
372-
site1 = Site.objects.create(name='Site 1', slug='site-1')
373-
site2 = Site.objects.create(name='Site 2', slug='site-2')
374-
site3 = Site.objects.create(name='Site 3', slug='site-3')
500+
# Create branch
501+
branch = self._create_and_provision_branch()
375502

376-
site1_id = site1.id
377-
site2_id = site2.id
378-
site3_id = site3.id
503+
# In main: create site with slug that will conflict
504+
site_main = Site.objects.create(name='Main Site', slug='conflict-slug')
505+
site_main_id = site_main.id
506+
507+
# Create a request context for event tracking
508+
request = RequestFactory().get(reverse('home'))
509+
request.id = uuid.uuid4()
510+
request.user = self.user
511+
512+
# In branch: create site with same slug (conflicts), update it, then delete it
513+
with activate_branch(branch), event_tracking(request):
514+
site_branch = Site.objects.create(name='Branch Site', slug='conflict-slug')
515+
site_branch_id = site_branch.id
516+
517+
# Update description
518+
site_branch.description = 'Updated in branch'
519+
site_branch.save()
520+
521+
# Delete the site
522+
site_branch.delete()
523+
524+
# Merge branch - should succeed (branch site is skipped)
525+
branch.merge(user=self.user, commit=True)
379526

527+
# Verify main schema - only main site exists
528+
self.assertTrue(Site.objects.filter(id=site_main_id).exists())
529+
self.assertFalse(Site.objects.filter(id=site_branch_id).exists())
530+
self.assertEqual(Site.objects.filter(slug='conflict-slug').count(), 1)
531+
532+
# Verify branch status
533+
branch.refresh_from_db()
534+
self.assertEqual(branch.status, BranchStatusChoices.MERGED)
535+
536+
def test_merge_slug_update_causes_then_resolves_conflict(self):
537+
"""
538+
Test create object, update to conflicting unique constraint, then update to resolve.
539+
Verifies final non-conflicting state merges successfully.
540+
"""
380541
# Create branch
381542
branch = self._create_and_provision_branch()
382543

544+
# In main: create site that will have slug conflict
545+
site_main = Site.objects.create(name='Main Site', slug='conflict-slug')
546+
site_main_id = site_main.id
547+
383548
# Create a request context for event tracking
384549
request = RequestFactory().get(reverse('home'))
385-
request.id = uuid.uuid4() # Set request id for ObjectChange tracking
550+
request.id = uuid.uuid4()
386551
request.user = self.user
387552

388-
# In branch: delete sites in specific order
553+
# In branch: create site with non-conflicting slug, update to conflict, then resolve
389554
with activate_branch(branch), event_tracking(request):
390-
Site.objects.get(id=site1_id).delete()
391-
Site.objects.get(id=site3_id).delete()
392-
Site.objects.get(id=site2_id).delete()
555+
site_branch = Site.objects.create(name='Branch Site', slug='no-conflict')
556+
site_branch_id = site_branch.id
393557

394-
# Merge branch
558+
# Update to conflicting slug
559+
site_branch.slug = 'conflict-slug'
560+
site_branch.save()
561+
562+
# Update again to resolve conflict
563+
site_branch.slug = 'resolved-slug'
564+
site_branch.save()
565+
566+
# Merge branch - should succeed (final state has no conflict)
395567
branch.merge(user=self.user, commit=True)
396568

397-
# Verify all deleted
398-
self.assertEqual(Site.objects.count(), 0)
569+
# Verify main schema
570+
self.assertTrue(Site.objects.filter(id=site_main_id).exists())
571+
self.assertTrue(Site.objects.filter(id=site_branch_id).exists())
572+
573+
site_main = Site.objects.get(id=site_main_id)
574+
self.assertEqual(site_main.slug, 'conflict-slug')
399575

400-
# Verify merge succeeded
576+
site_branch = Site.objects.get(id=site_branch_id)
577+
self.assertEqual(site_branch.slug, 'resolved-slug')
578+
579+
# Verify branch status
401580
branch.refresh_from_db()
402581
self.assertEqual(branch.status, BranchStatusChoices.MERGED)

0 commit comments

Comments
 (0)