@@ -2675,120 +2675,24 @@ def setUpTestData(cls):
26752675 },
26762676 ]
26772677
2678- def test_device_filter_pagination_distinct (self ):
2679- """
2680- Test that filtering MAC addresses by device_id via API returns distinct results
2681- across pagination boundaries. Creates many interfaces with the same MAC address
2682- to force the pagination duplication bug to manifest.
2683- """
2684- # Add the required permission for this test
2678+ def test_mac_address_pagination_duplicates (self ):
26852679 self .add_permissions ('dcim.view_macaddress' )
2686-
2687- # Create a device with many interfaces all having the same MAC address
2688- device = create_test_device ('Pagination Test Device' )
2689- shared_mac_address = '18:2A:D3:65:90:2E' # Use the same MAC from your original issue
2690-
2691- # Create many interfaces with the same MAC address to trigger the pagination bug
2692- # This simulates the scenario where the same MAC address appears on multiple interfaces
2693- # Without .distinct(), the same MAC address object could appear multiple times across pages
2694- for i in range (500 ): # Create more to really stress test pagination
2680+ device = create_test_device ('Device 2' )
2681+ shared_mac = '00:00:00:00:00:07'
2682+ for i in range (5 ):
26952683 interface = Interface .objects .create (
2696- device = device ,
2697- name = f'dummy{ i :03d} ' ,
2698- type = '1000base-t'
2684+ device = device , name = f'Interface { i :03d} ' , type = '1000base-t'
26992685 )
2700-
2701- # ALL interfaces get the same MAC address - this triggers the pagination duplication bug
2702- MACAddress .objects .create (
2703- mac_address = shared_mac_address ,
2704- assigned_object = interface
2705- )
2706-
2707- # Test API pagination with device_id filter - simulate your script's behavior
2686+ MACAddress .objects .create (mac_address = shared_mac , assigned_object = interface )
27082687 url = reverse ('dcim-api:macaddress-list' )
2709- expected_count = 500 # Should be exactly 500 unique MAC address objects
2710-
2711- # Test the exact pagination scenario from your script
2712- # Your script uses limit=100, so let's test with that
27132688 all_mac_ids = set ()
2714- total_collected = 0
2715-
2716- # Page 1: limit=100 (like your dummy-test.py)
2717- response = self .client .get (
2718- f'{ url } ?device_id={ device .pk } &limit=100' ,
2719- format = 'json' ,
2720- ** self .header
2721- )
2722- self .assertHttpStatus (response , status .HTTP_200_OK )
2723- self .assertEqual (response .data ['count' ], expected_count )
2724- self .assertEqual (len (response .data ['results' ]), 100 )
2725-
2726- page_1_ids = {result ['id' ] for result in response .data ['results' ]}
2727- all_mac_ids .update (page_1_ids )
2728- total_collected += len (response .data ['results' ])
2729-
2730- # Continue pagination like your script does
2731- next_url = response .data .get ('next' )
2732- page_count = 1
2733-
2734- while next_url and page_count < 10 : # Limit to prevent infinite loop
2735- page_count += 1
2736- # Extract just the query parameters from the next URL
2737- from urllib .parse import urlparse , parse_qs
2738- parsed = urlparse (next_url )
2739- query_params = parse_qs (parsed .query )
2740-
2741- # Build the request
2742- params = {}
2743- for key , value_list in query_params .items ():
2744- if value_list :
2745- params [key ] = value_list [0 ]
2746-
2747- response = self .client .get (url , params , format = 'json' , ** self .header )
2689+ for page in range (1 , 6 ):
2690+ response = self .client .get (
2691+ f'{ url } ?device_id={ device .pk } &limit=1&offset={ 1 * (page - 1 )} ' ,
2692+ format = 'json' , ** self .header
2693+ )
27482694 self .assertHttpStatus (response , status .HTTP_200_OK )
2749-
27502695 page_ids = {result ['id' ] for result in response .data ['results' ]}
2751-
2752- # Check for duplicate IDs across pages - this is where the bug would show
2753- duplicates = all_mac_ids & page_ids
2754- self .assertEqual (len (duplicates ), 0 ,
2755- f"Found duplicate MAC address IDs between pages: { duplicates } " )
2756-
2696+ self .assertEqual (len (all_mac_ids & page_ids ), 0 )
27572697 all_mac_ids .update (page_ids )
2758- total_collected += len (response .data ['results' ])
2759- next_url = response .data .get ('next' )
2760-
2761- # The critical test: without .distinct(), total_collected could be > expected_count
2762- # because the same MAC address object appears multiple times
2763- self .assertEqual (total_collected , expected_count ,
2764- f"Total MAC addresses collected ({ total_collected } ) should equal expected ({ expected_count } )" )
2765-
2766- # Verify all collected IDs are unique
2767- self .assertEqual (len (all_mac_ids ), expected_count ,
2768- f"Should have { expected_count } unique MAC address IDs, got { len (all_mac_ids )} " )
2769-
2770- # Additional verification: all MAC addresses should have the same value
2771- # Get all results in one go to verify
2772- response_all = self .client .get (
2773- f'{ url } ?device_id={ device .pk } &limit=0' , # No pagination limit
2774- format = 'json' ,
2775- ** self .header
2776- )
2777- self .assertHttpStatus (response , status .HTTP_200_OK )
2778-
2779- # Count interfaces like your original script does
2780- interface_mac_count = {}
2781- for result in response_all .data ['results' ]:
2782- if result ['assigned_object' ]:
2783- interface_name = result ['assigned_object' ]['name' ]
2784- if interface_name not in interface_mac_count :
2785- interface_mac_count [interface_name ] = []
2786- interface_mac_count [interface_name ].append (result )
2787-
2788- # In your scenario, you had 1000 MACs on 997 interfaces due to the bug
2789- # Here, we should have exactly 500 MACs on 500 interfaces
2790- total_macs_via_interfaces = sum (len (macs ) for macs in interface_mac_count .values ())
2791- self .assertEqual (total_macs_via_interfaces , expected_count ,
2792- f"Total MACs via interface grouping should be { expected_count } " )
2793- self .assertEqual (len (interface_mac_count ), expected_count ,
2794- f"Should have exactly { expected_count } interfaces with MAC addresses" )
2698+ self .assertEqual (len (all_mac_ids ), 5 )
0 commit comments