Skip to content

Generic API test harness miscompares ArrayField(IntegerRangeField) values (NumericRange vs inclusive pairs) #20491

@pheus

Description

@pheus

NetBox Edition

NetBox Community

NetBox Version

v4.4.2

Python Version

3.12

Steps to Reproduce

  1. Define a model with an array of integer ranges:
    from django.db import models
    from django.contrib.postgres.fields import ArrayField, IntegerRangeField
    
    class DemoModel(models.Model):
        name = models.CharField(max_length=50)
        port_ranges = ArrayField(base_field=IntegerRangeField(), blank=True, default=list)
  2. Expose it in the API using the existing range serializer:
    from netbox.api.serializers import NetBoxModelSerializer
    from netbox.api.fields import IntegerRangeSerializer
    
    class DemoSerializer(NetBoxModelSerializer):
        # Inclusive [start, end] pairs; list handled by many=True
        port_ranges = IntegerRangeSerializer(many=True, required=False)
    
        class Meta:
            model = DemoModel
            fields = ("id", "name", "port_ranges")
  3. Write an APIViewTestCase that creates/updates using inclusive pairs:
    from netbox.utilities.testing import APIViewTestCases
    
    class DemoAPIViewTestCase(APIViewTestCases.APIViewTestCase):
        model = DemoModel
    
        @classmethod
        def setUpTestData(cls):
            cls.create_data = [
                {"name": "demo1", "port_ranges": [[22, 22], [443, 443]]}
            ]
  4. Run the tests.

Expected Behavior

When api=True, model_to_dict() should normalize ArrayField(IntegerRangeField) values to inclusive pairs [[lo, hi], ...], matching:

  • the API serializer representation (IntegerRangeSerializer)
  • the established VLANGroup.vid_ranges representation.

Observed Behavior

utilities.testing.api::test_update_objectassertInstanceEqual compares request data to model_to_dict(instance, api=True). For ArrayField(IntegerRangeField), the model dict includes psycopg ranges:

AssertionError:
- 'port_ranges': [Range(22, 23, '[)'), Range(443, 444, '[)')],
+ 'port_ranges': [[22, 22], [443, 443]],

Root Cause

PostgreSQL canonicalizes discrete integer ranges to half‑open [lo, hi). The test harness directly compares these psycopg Range objects against the inclusive JSON pairs submitted by the test case.

Proposed Fix

Normalize arrays of numeric ranges within the api=True branch of utilities.testing.base:model_to_dict():

# utilities/testing/base.py (inside model_to_dict(), within the `api` branch)
elif type(field) is ArrayField and issubclass(type(field.base_field), RangeField):
    # Convert half-open [lo, hi) to inclusive [lo, hi-1]
    model_dict[key] = [[r.lower, r.upper - 1] for r in value]

This mirrors the non‑API branch (which already special‑cases range arrays for forms/CSV) and aligns the generic API tests with the inclusive JSON representation used by NetBox serializers.

Minimal End-to-End Example

  • Model: ArrayField(IntegerRangeField) as above.
  • Serializer: IntegerRangeSerializer(many=True, required=False).
  • Test payload: {"name": "demo1", "port_ranges": [[22, 22], [443, 443]]}.
  • Before fix: Fails: Range(22, 23, '[)') vs [[22, 22]].
  • After fix: Passes: both sides compare as [[22, 22], [443, 443]].

Notes

  • This change affects only the test utility normalization for api=True. It does not alter database storage, API behavior, or public schemas.
  • It brings the generic test harness in line with the inclusive‑pair API contract already used elsewhere (e.g., VLAN ID ranges).

Metadata

Metadata

Labels

netboxstatus: acceptedThis issue has been accepted for implementationtype: bugA confirmed report of unexpected behavior in the application

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions