PO status fix (#12885)

* Fix for PurchaseOrder status code view

- Order dependent, due to new viewset

* Add regression tests for status code API endpoints
This commit is contained in:
Oliver
2026-09-19 11:54:07 +10:00
committed by GitHub
parent ad69a35dfb
commit fcb1e4f23e
4 changed files with 124 additions and 2 deletions
+15
View File
@@ -1426,6 +1426,21 @@ class BuildListTest(BuildAPITest):
assert_fnc=lambda x: x.data['results'][0],
)
def test_status_codes_endpoint(self):
"""The 'build/status/' endpoint must resolve to the status-codes view.
Regression test: ensures the literal 'status/' path is not shadowed by the
'build/<pk>/' detail route it sits alongside in the same urlconf.
"""
response = self.get(reverse('api-build-status-codes'), expected_code=200)
self.assertIn('status_class', response.data)
self.assertIn('values', response.data)
self.assertIn('PENDING', response.data['values'])
self.assertEqual(
response.data['values']['PENDING']['key'], BuildStatus.PENDING.value
)
class BuildOutputCreateTest(BuildAPITest):
"""Unit test for creating build output via API."""
+2 -2
View File
@@ -2695,8 +2695,6 @@ class OrderCalendarExport(ICalFeed):
order_api_urls = [
# Purchase Order, Line Item, and Extra Line API endpoints via ViewSet router
path('', include(order_router.urls)),
# Purchase order status code information (requires custom kwargs)
path(
'po/status/',
@@ -2704,6 +2702,8 @@ order_api_urls = [
{StatusView.MODEL_REF: PurchaseOrderStatus},
name='api-po-status-codes',
),
# Purchase Order, Line Item, and Extra Line API endpoints via ViewSet router
path('', include(order_router.urls)),
# API endpoints for sales orders
path(
'so/',
+77
View File
@@ -999,6 +999,27 @@ class PurchaseOrderTest(OrderTest):
self.assertIn('status_text', result)
self.assertIsNotNone(result['status_text'])
def test_status_codes_endpoint(self):
"""The 'po/status/' endpoint must resolve to the status-codes view.
Regression test: PurchaseOrder is served by a ViewSet router, whose
generated detail route ('po/<pk>/') uses DRF's default, permissive pk
lookup regex. That regex is happy to match the literal segment 'status'
as a pk, so if the router is registered ahead of the 'po/status/' path in
the urlconf, this endpoint gets swallowed by
PurchaseOrderViewSet.retrieve(pk='status') instead of reaching StatusView -
returning a 404 (no such PurchaseOrder) rather than the status code data.
"""
response = self.get(reverse('api-po-status-codes'), expected_code=200)
# A genuine StatusView response - not a PurchaseOrder-detail-shaped 404
self.assertIn('status_class', response.data)
self.assertIn('values', response.data)
self.assertIn('PENDING', response.data['values'])
self.assertEqual(
response.data['values']['PENDING']['key'], PurchaseOrderStatus.PENDING.value
)
class PurchaseOrderLineItemTest(OrderTest):
"""Unit tests for PurchaseOrderLineItems."""
@@ -2489,6 +2510,25 @@ class SalesOrderTest(OrderTest):
self.assertIn('status_text', result)
self.assertIsNotNone(result['status_text'])
def test_status_codes_endpoint(self):
"""The 'so/status/' endpoint must resolve to the status-codes view.
SalesOrder is not (yet) served by a ViewSet router - its detail route uses
Django's '<int:pk>' path converter, which is not vulnerable to the
router-based bug affecting PurchaseOrder (see PurchaseOrderTest for
details). This is a coverage test guarding against a future regression,
e.g. if SalesOrder is migrated to a router-based viewset without also
restricting the pk lookup pattern.
"""
response = self.get(reverse('api-so-status-codes'), expected_code=200)
self.assertIn('status_class', response.data)
self.assertIn('values', response.data)
self.assertIn('PENDING', response.data['values'])
self.assertEqual(
response.data['values']['PENDING']['key'], SalesOrderStatus.PENDING.value
)
class SalesOrderLineItemTest(OrderTest):
"""Tests for the SalesOrderLineItem API."""
@@ -3888,6 +3928,23 @@ class ReturnOrderTests(InvenTreeAPITestCase):
reverse('api-return-order-detail', kwargs={'pk': 1}), ['customer_detail']
)
def test_status_codes_endpoint(self):
"""The 'ro/status/' endpoint must resolve to the status-codes view.
ReturnOrder is not (yet) served by a ViewSet router - its detail route uses
Django's '<int:pk>' path converter, which is not vulnerable to the
router-based bug affecting PurchaseOrder (see PurchaseOrderTest for
details). This is a coverage test guarding against a future regression.
"""
response = self.get(reverse('api-return-order-status-codes'), expected_code=200)
self.assertIn('status_class', response.data)
self.assertIn('values', response.data)
self.assertIn('PENDING', response.data['values'])
self.assertEqual(
response.data['values']['PENDING']['key'], ReturnOrderStatus.PENDING.value
)
class ReturnOrderLineItemTests(InvenTreeAPITestCase):
"""Unit tests for ReturnOrderLineItem API endpoints."""
@@ -4051,6 +4108,26 @@ class ReturnOrderLineItemTests(InvenTreeAPITestCase):
self.assertEqual(models.ReturnOrderExtraLine.objects.count(), n - 2)
def test_status_codes_endpoint(self):
"""The 'ro-line/status/' endpoint must resolve to the status-codes view.
ReturnOrderLineItem is not (yet) served by a ViewSet router - its detail
route uses Django's '<int:pk>' path converter, which is not vulnerable to
the router-based bug affecting PurchaseOrder (see PurchaseOrderTest for
details). This is a coverage test guarding against a future regression.
"""
response = self.get(
reverse('api-return-order-line-status-codes'), expected_code=200
)
self.assertIn('status_class', response.data)
self.assertIn('values', response.data)
self.assertIn('PENDING', response.data['values'])
self.assertEqual(
response.data['values']['PENDING']['key'],
ReturnOrderLineStatus.PENDING.value,
)
class ExtraLineTotalPriceTest(InvenTreeAPITestCase):
"""Unit tests for the 'total_price' field on ExtraLine API endpoints.
+30
View File
@@ -2417,6 +2417,19 @@ class StockItemTest(StockAPITestCase):
item.refresh_from_db()
self.assertEqual(item.batch, 'NEW-BATCH-CODE')
def test_status_codes_endpoint(self):
"""The 'stock/status/' endpoint must resolve to the status-codes view.
Regression test: ensures the literal 'status/' path is not shadowed by the
'stock/<pk>/' detail route it sits alongside in the same urlconf.
"""
response = self.get(reverse('api-stock-status-codes'), expected_code=200)
self.assertIn('status_class', response.data)
self.assertIn('values', response.data)
self.assertIn('OK', response.data['values'])
self.assertEqual(response.data['values']['OK']['key'], StockStatus.OK.value)
class StockItemDisassembleTest(StockAPITestCase):
"""Series of API tests for the StockItem disassembly endpoint."""
@@ -4051,6 +4064,23 @@ class StockTrackingTest(StockAPITestCase):
assert_fnc=lambda x: x.data['results'][0],
)
def test_status_codes_endpoint(self):
"""The 'track/status/' endpoint must resolve to the status-codes view.
Regression test: ensures the literal 'status/' path is not shadowed by the
'track/<pk>/' detail route it sits alongside in the same urlconf.
"""
response = self.get(
reverse('api-stock-tracking-status-codes'), expected_code=200
)
self.assertIn('status_class', response.data)
self.assertIn('values', response.data)
self.assertIn('CREATED', response.data['values'])
self.assertEqual(
response.data['values']['CREATED']['key'], StockHistoryCode.CREATED.value
)
class StockAssignTest(StockAPITestCase):
"""Unit tests for the stock assignment API endpoint, where stock items are manually assigned to a customer."""