2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-25 18:37:38 +00:00

Merge branch 'make-fields-filterable' of https://github.com/matmair/InvenTree into make-fields-filterable

This commit is contained in:
Matthias Mair
2025-10-13 23:48:16 +02:00
6 changed files with 146 additions and 243 deletions

View File

@@ -8,7 +8,7 @@ import re
import time import time
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Callable, Optional, Union
from unittest import mock from unittest import mock
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@@ -728,6 +728,63 @@ class InvenTreeAPITestCase(
"""Assert that dictionary 'a' is a subset of dictionary 'b'.""" """Assert that dictionary 'a' is a subset of dictionary 'b'."""
self.assertEqual(b, b | a) self.assertEqual(b, b | a)
def run_output_test(
self,
url: str,
test_cases: list[Union[tuple[str, str], str]],
additional_params: Optional[dict] = None,
assert_subset: bool = False,
assert_fnc: Optional[Callable] = None,
):
"""Run a series of tests against the provided URL.
Arguments:
url: The URL to test
test_cases: A list of tuples of the form (parameter_name, response_field_name)
additional_params: Additional request parameters to include in the request
assert_subset: If True, make the assertion against the first item in the response rather than the entire response
assert_fnc: If provided, call this function with the response data and make the assertion against the return value
"""
def get_response(response):
if assert_subset:
return response.data[0]
if assert_fnc:
return assert_fnc(response)
return response.data
for case in test_cases:
if isinstance(case, str):
param = case
field = case
else:
param, field = case
# Test with parameter set to 'true'
response = self.get(
url,
{param: 'true', **(additional_params or {})},
expected_code=200,
msg=f'Testing {param}=true returns anything but 200',
)
self.assertIn(
field,
get_response(response),
f"Field '{field}' should be present when {param}=true",
)
# Test with parameter set to 'false'
response = self.get(
url,
{param: 'false', **(additional_params or {})},
expected_code=200,
msg=f'Testing {param}=false returns anything but 200',
)
self.assertNotIn(
field,
get_response(response),
f"Field '{field}' should NOT be present when {param}=false",
)
@override_settings( @override_settings(
SITE_URL='http://testserver', CSRF_TRUSTED_ORIGINS=['http://testserver'] SITE_URL='http://testserver', CSRF_TRUSTED_ORIGINS=['http://testserver']

View File

@@ -1474,42 +1474,15 @@ class BuildLineTests(BuildAPITest):
def test_output_options(self): def test_output_options(self):
"""Test output options for the BuildLine endpoint.""" """Test output options for the BuildLine endpoint."""
url = reverse('api-build-line-detail', kwargs={'pk': 2}) self.run_output_test(
reverse('api-build-line-detail', kwargs={'pk': 2}),
# Test cases: (parameter_name, response_field_name) [
test_cases = [ 'bom_item_detail',
('bom_item_detail', 'bom_item_detail'), 'assembly_detail',
('assembly_detail', 'assembly_detail'), 'part_detail',
('part_detail', 'part_detail'), 'build_detail',
('build_detail', 'build_detail'), 'allocations',
('allocations', 'allocations'), ],
]
for param, field in test_cases:
# Test with parameter set to 'true'
response = self.get(
url,
{param: 'true'},
expected_code=200,
msg=f'Testing {param}=true returns anything but 200',
)
self.assertIn(
field,
response.data,
f"Field '{field}' should be present when {param}=true",
)
# Test with parameter set to 'false'
response = self.get(
url,
{param: 'false'},
expected_code=200,
msg=f'Testing {param}=false returns anything but 200',
)
self.assertNotIn(
field,
response.data,
f"Field '{field}' should NOT be present when {param}=false",
) )
def test_filter_consumed(self): def test_filter_consumed(self):

View File

@@ -538,30 +538,10 @@ class ManufacturerTest(InvenTreeAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test the output options for SupplierPart detail.""" """Test the output options for SupplierPart detail."""
url = reverse('api-manufacturer-part-list') self.run_output_test(
reverse('api-manufacturer-part-list'),
# Test cases: (parameter_name, response_field_name) ['part_detail', 'manufacturer_detail', ('pretty', 'pretty_name')],
test_cases = [ assert_subset=True,
('part_detail', 'part_detail'),
('manufacturer_detail', 'manufacturer_detail'),
('pretty', 'pretty_name'),
]
for param, field in test_cases:
# Test with parameter set to 'true'
response = self.get(url, {param: 'true', 'limit': 1}, expected_code=200)
self.assertIn(
field,
response.data['results'][0],
f"Field '{field}' should be present when {param}='true'",
)
# Test with parameter set to 'false'
response = self.get(url, {param: 'false', 'limit': 1}, expected_code=200)
self.assertNotIn(
field,
response.data['results'][0],
f"Field '{field}' should not be present when {param}='false'",
) )
@@ -603,31 +583,14 @@ class SupplierPartTest(InvenTreeAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test the output options for SupplierPart detail.""" """Test the output options for SupplierPart detail."""
sp = SupplierPart.objects.all().first() sp = SupplierPart.objects.all().first()
url = reverse('api-supplier-part-detail', kwargs={'pk': sp.pk}) self.run_output_test(
reverse('api-supplier-part-detail', kwargs={'pk': sp.pk}),
# Test cases: (parameter_name, response_field_name) [
test_cases = [ 'part_detail',
('part_detail', 'part_detail'), 'supplier_detail',
('supplier_detail', 'supplier_detail'), 'manufacturer_detail',
('manufacturer_detail', 'manufacturer_detail'),
('pretty', 'pretty_name'), ('pretty', 'pretty_name'),
] ],
for param, field in test_cases:
# Test with parameter set to 'true'
response = self.get(url, {param: 'true'}, expected_code=200)
self.assertIn(
field,
response.data,
f"Field '{field}' should be present when {param}='true'",
)
# Test with parameter set to 'false'
response = self.get(url, {param: 'false'}, expected_code=200)
self.assertNotIn(
field,
response.data,
f"Field '{field}' should not be present when {param}='false'",
) )
def test_available(self): def test_available(self):
@@ -789,27 +752,11 @@ class SupplierPriceBreakAPITest(InvenTreeAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test the output options for SupplierPart price break list.""" """Test the output options for SupplierPart price break list."""
url = reverse('api-part-supplier-price-list') self.run_output_test(
test_cases = [ reverse('api-part-supplier-price-list'),
('part_detail', 'part_detail'), ['part_detail', 'supplier_detail'],
('supplier_detail', 'supplier_detail'), additional_params={'limit': 1},
] assert_fnc=lambda x: x.data['results'][0],
for param, field in test_cases:
# Test with parameter set to 'true'
response = self.get(url, {param: 'true', 'limit': 1}, expected_code=200)
self.assertIn(
field,
response.data['results'][0],
f"Field '{field}' should be present when {param}='true'",
)
# Test with parameter set to 'false'
response = self.get(url, {param: 'false', 'limit': 1}, expected_code=200)
self.assertNotIn(
field,
response.data['results'][0],
f"Field '{field}' should not be present when {param}='false'",
) )
def test_supplier_price_break_list(self): def test_supplier_price_break_list(self):

View File

@@ -315,12 +315,9 @@ class PurchaseOrderTest(OrderTest):
def test_output_options(self): def test_output_options(self):
"""Test the various output options for the PurchaseOrder detail endpoint.""" """Test the various output options for the PurchaseOrder detail endpoint."""
url = reverse('api-po-detail', kwargs={'pk': 1}) self.run_output_test(
response = self.get(url, {'supplier_detail': 'true'}, expected_code=200) reverse('api-po-detail', kwargs={'pk': 1}), ['supplier_detail']
self.assertIn('supplier_detail', response.data) )
response = self.get(url, {'supplier_detail': 'false'}, expected_code=200)
self.assertNotIn('supplier_detail', response.data)
def test_po_operations(self): def test_po_operations(self):
"""Test that we can create / edit and delete a PurchaseOrder via the API.""" """Test that we can create / edit and delete a PurchaseOrder via the API."""
@@ -863,17 +860,10 @@ class PurchaseOrderLineItemTest(OrderTest):
def test_output_options(self): def test_output_options(self):
"""Test PurchaseOrderLineItem output option endpoint.""" """Test PurchaseOrderLineItem output option endpoint."""
url = reverse('api-po-line-detail', kwargs={'pk': 1}) self.run_output_test(
reverse('api-po-line-detail', kwargs={'pk': 1}),
response = self.get(url, {'part_detail': 'true'}, expected_code=200) ['part_detail', 'order_detail'],
self.assertIn('part_detail', response.data) )
response = self.get(url, {'part_detail': 'false'}, expected_code=200)
self.assertNotIn('part_detail', response.data)
response = self.get(url, {'order_detail': 'true'}, expected_code=200)
self.assertIn('order_detail', response.data)
response = self.get(url, {'order_detail': 'false'}, expected_code=200)
self.assertNotIn('order_detail', response.data)
class PurchaseOrderDownloadTest(OrderTest): class PurchaseOrderDownloadTest(OrderTest):
@@ -1774,11 +1764,9 @@ class SalesOrderTest(OrderTest):
def test_output_options(self): def test_output_options(self):
"""Test the output options for the SalesOrder detail endpoint.""" """Test the output options for the SalesOrder detail endpoint."""
url = reverse('api-so-detail', kwargs={'pk': 1}) self.run_output_test(
response = self.get(url, {'customer_detail': True}, expected_code=200) reverse('api-so-detail', kwargs={'pk': 1}), ['customer_detail']
self.assertIn('customer_detail', response.data) )
response = self.get(url, {'customer_detail': False}, expected_code=200)
self.assertNotIn('customer_detail', response.data)
class SalesOrderLineItemTest(OrderTest): class SalesOrderLineItemTest(OrderTest):
@@ -1943,14 +1931,10 @@ class SalesOrderLineItemTest(OrderTest):
def test_output_options(self): def test_output_options(self):
"""Test the various output options for the SalesOrderLineItem detail endpoint.""" """Test the various output options for the SalesOrderLineItem detail endpoint."""
url = reverse('api-so-line-detail', kwargs={'pk': 1}) self.run_output_test(
reverse('api-so-line-detail', kwargs={'pk': 1}),
options = ['part_detail', 'order_detail', 'customer_detail'] ['part_detail', 'order_detail', 'customer_detail'],
for option in options: )
response = self.get(url, {f'{option}': True}, expected_code=200)
self.assertIn(option, response.data)
response = self.get(url, {f'{option}': False}, expected_code=200)
self.assertNotIn(option, response.data)
class SalesOrderDownloadTest(OrderTest): class SalesOrderDownloadTest(OrderTest):
@@ -2296,20 +2280,17 @@ class SalesOrderAllocateTest(OrderTest):
def test_output_options(self): def test_output_options(self):
"""Test the various output options for the SalesOrderAllocation detail endpoint.""" """Test the various output options for the SalesOrderAllocation detail endpoint."""
url = reverse('api-so-allocation-list') self.run_output_test(
reverse('api-so-allocation-list'),
options = [ [
'part_detail', 'part_detail',
'item_detail', 'item_detail',
'order_detail', 'order_detail',
'location_detail', 'location_detail',
'customer_detail', 'customer_detail',
] ],
for option in options: assert_subset=True,
response = self.get(url, {f'{option}': True}, expected_code=200) )
self.assertIn(option, response.data[0])
response = self.get(url, {f'{option}': False}, expected_code=200)
self.assertNotIn(option, response.data[0])
class ReturnOrderTests(InvenTreeAPITestCase): class ReturnOrderTests(InvenTreeAPITestCase):
@@ -2679,14 +2660,9 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test the various output options for the ReturnOrder detail endpoint.""" """Test the various output options for the ReturnOrder detail endpoint."""
url = reverse('api-return-order-detail', kwargs={'pk': 1}) self.run_output_test(
reverse('api-return-order-detail', kwargs={'pk': 1}), ['customer_detail']
options = ['customer_detail'] )
for option in options:
response = self.get(url, {f'{option}': True}, expected_code=200)
self.assertIn(option, response.data)
response = self.get(url, {f'{option}': False}, expected_code=200)
self.assertNotIn(option, response.data)
class ReturnOrderLineItemTests(InvenTreeAPITestCase): class ReturnOrderLineItemTests(InvenTreeAPITestCase):
@@ -2749,17 +2725,10 @@ class ReturnOrderLineItemTests(InvenTreeAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test output options for detail endpoint.""" """Test output options for detail endpoint."""
url = reverse('api-return-order-line-detail', kwargs={'pk': 1}) self.run_output_test(
options = ['part_detail', 'item_detail', 'order_detail'] reverse('api-return-order-line-detail', kwargs={'pk': 1}),
['part_detail', 'item_detail', 'order_detail'],
for option in options: )
# Test with option enabled
response = self.get(url, {option: True}, expected_code=200)
self.assertIn(option, response.data)
# Test with option disabled
response = self.get(url, {option: False}, expected_code=200)
self.assertNotIn(option, response.data)
def test_update(self): def test_update(self):
"""Test updating ReturnOrderLineItem.""" """Test updating ReturnOrderLineItem."""

View File

@@ -2695,13 +2695,10 @@ class BomItemTest(InvenTreeAPITestCase):
# TODO add test for (pricing, pricing_min) # TODO add test for (pricing, pricing_min)
def test_output_options(self): def test_output_options(self):
"""Test that various output options work as expected.""" """Test that various output options work as expected."""
url = reverse('api-bom-item-detail', kwargs={'pk': 3}) self.run_output_test(
options = ['can_build', 'part_detail', 'sub_part_detail', 'substitutes'] reverse('api-bom-item-detail', kwargs={'pk': 3}),
for option in options: ['can_build', 'part_detail', 'sub_part_detail', 'substitutes'],
response = self.get(url, {f'{option}': True}, expected_code=200) )
self.assertIn(option, response.data)
response = self.get(url, {f'{option}': False}, expected_code=200)
self.assertNotIn(option, response.data)
def test_add_bom_item(self): def test_add_bom_item(self):
"""Test that we can create a new BomItem via the API.""" """Test that we can create a new BomItem via the API."""

View File

@@ -265,13 +265,9 @@ class StockLocationTest(StockAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test output options.""" """Test output options."""
url = reverse('api-location-detail', kwargs={'pk': 1}) self.run_output_test(
reverse('api-location-detail', kwargs={'pk': 1}), [('path_detail', 'path')]
response = self.get(url, {'path_detail': 'true'}, expected_code=200) )
self.assertIn('path', response.data)
response = self.get(url, {'path_detail': 'false'}, expected_code=200)
self.assertNotIn('path', response.data)
def test_stock_location_structural(self): def test_stock_location_structural(self):
"""Test the effectiveness of structural stock locations. """Test the effectiveness of structural stock locations.
@@ -1529,32 +1525,15 @@ class StockItemTest(StockAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test the output options for StockItemt detail.""" """Test the output options for StockItemt detail."""
url = reverse('api-stock-detail', kwargs={'pk': 1}) self.run_output_test(
reverse('api-stock-detail', kwargs={'pk': 1}),
# Test cases: (parameter_name, response_field_name) [
test_cases = [ 'part_detail',
('part_detail', 'part_detail'),
('path_detail', 'location_path'), ('path_detail', 'location_path'),
('supplier_part_detail', 'supplier_part_detail'), 'supplier_part_detail',
('location_detail', 'location_detail'), 'location_detail',
('tests', 'tests'), 'tests',
] ],
for param, field in test_cases:
# Test with parameter set to 'true'
response = self.get(url, {param: 'true'}, expected_code=200)
self.assertIn(
field,
response.data,
f"Field '{field}' should be present when {param}='true'",
)
# Test with parameter set to 'false'
response = self.get(url, {param: 'false'}, expected_code=200)
self.assertNotIn(
field,
response.data,
f"Field '{field}' should not be present when {param}='false'",
) )
def test_install(self): def test_install(self):
@@ -2223,19 +2202,10 @@ class StockTestResultTest(StockAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test output options for single item retrieval.""" """Test output options for single item retrieval."""
url = reverse('api-stock-test-result-detail', kwargs={'pk': 1}) self.run_output_test(
reverse('api-stock-test-result-detail', kwargs={'pk': 1}),
response = self.get(url, {'user_detail': 'true'}, expected_code=200) ['user_detail', 'template_detail'],
self.assertIn('user_detail', response.data) )
response = self.get(url, {'user_detail': 'false'}, expected_code=200)
self.assertNotIn('user_detail', response.data)
response = self.get(url, {'template_detail': 'true'}, expected_code=200)
self.assertIn('template_detail', response.data)
response = self.get(url, {'template_detail': 'false'}, expected_code=200)
self.assertNotIn('template_detail', response.data)
class StockTrackingTest(StockAPITestCase): class StockTrackingTest(StockAPITestCase):
@@ -2334,23 +2304,13 @@ class StockTrackingTest(StockAPITestCase):
def test_output_options(self): def test_output_options(self):
"""Test output options.""" """Test output options."""
url = self.get_url() self.run_output_test(
response = self.client.get( self.get_url(),
url, {'item_detail': True, 'user_detail': True, 'limit': 2} ['item_detail', 'user_detail'],
additional_params={'limit': 2},
assert_fnc=lambda x: x.data['results'][0],
) )
for item in response.data['results']:
self.assertIn('item_detail', item)
self.assertIn('user_detail', item)
response = self.client.get(
url, {'item_detail': False, 'user_detail': False, 'limit': 2}
)
for item in response.data['results']:
self.assertNotIn('item_detail', item)
self.assertNotIn('user_detail', item)
class StockAssignTest(StockAPITestCase): class StockAssignTest(StockAPITestCase):
"""Unit tests for the stock assignment API endpoint, where stock items are manually assigned to a customer.""" """Unit tests for the stock assignment API endpoint, where stock items are manually assigned to a customer."""