2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-14 21:22:20 +00:00

refactor(backend): reduce duplication in tests (#10579)

* refactor(backend): reduce duplication for tests

* fix tests

* fix text

* adjust last test
This commit is contained in:
Matthias Mair
2025-10-13 23:23:25 +02:00
committed by GitHub
parent f5955ff0e2
commit ac1eb85334
6 changed files with 146 additions and 243 deletions

View File

@@ -8,7 +8,7 @@ import re
import time
from contextlib import contextmanager
from pathlib import Path
from typing import Optional
from typing import Callable, Optional, Union
from unittest import mock
from django.contrib.auth import get_user_model
@@ -728,6 +728,63 @@ class InvenTreeAPITestCase(
"""Assert that dictionary 'a' is a subset of dictionary 'b'."""
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(
SITE_URL='http://testserver', CSRF_TRUSTED_ORIGINS=['http://testserver']

View File

@@ -1447,43 +1447,16 @@ class BuildLineTests(BuildAPITest):
def test_output_options(self):
"""Test output options for the BuildLine endpoint."""
url = reverse('api-build-line-detail', kwargs={'pk': 2})
# Test cases: (parameter_name, response_field_name)
test_cases = [
('bom_item_detail', 'bom_item_detail'),
('assembly_detail', 'assembly_detail'),
('part_detail', 'part_detail'),
('build_detail', 'build_detail'),
('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",
)
self.run_output_test(
reverse('api-build-line-detail', kwargs={'pk': 2}),
[
'bom_item_detail',
'assembly_detail',
'part_detail',
'build_detail',
'allocations',
],
)
def test_filter_consumed(self):
"""Filter for the 'consumed' status."""

View File

@@ -538,31 +538,11 @@ class ManufacturerTest(InvenTreeAPITestCase):
def test_output_options(self):
"""Test the output options for SupplierPart detail."""
url = reverse('api-manufacturer-part-list')
# Test cases: (parameter_name, response_field_name)
test_cases = [
('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'",
)
self.run_output_test(
reverse('api-manufacturer-part-list'),
['part_detail', 'manufacturer_detail', ('pretty', 'pretty_name')],
assert_subset=True,
)
class SupplierPartTest(InvenTreeAPITestCase):
@@ -603,32 +583,15 @@ class SupplierPartTest(InvenTreeAPITestCase):
def test_output_options(self):
"""Test the output options for SupplierPart detail."""
sp = SupplierPart.objects.all().first()
url = reverse('api-supplier-part-detail', kwargs={'pk': sp.pk})
# Test cases: (parameter_name, response_field_name)
test_cases = [
('part_detail', 'part_detail'),
('supplier_detail', 'supplier_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'}, 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'",
)
self.run_output_test(
reverse('api-supplier-part-detail', kwargs={'pk': sp.pk}),
[
'part_detail',
'supplier_detail',
'manufacturer_detail',
('pretty', 'pretty_name'),
],
)
def test_available(self):
"""Tests for updating the 'available' field."""
@@ -789,28 +752,12 @@ class SupplierPriceBreakAPITest(InvenTreeAPITestCase):
def test_output_options(self):
"""Test the output options for SupplierPart price break list."""
url = reverse('api-part-supplier-price-list')
test_cases = [
('part_detail', 'part_detail'),
('supplier_detail', 'supplier_detail'),
]
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'",
)
self.run_output_test(
reverse('api-part-supplier-price-list'),
['part_detail', 'supplier_detail'],
additional_params={'limit': 1},
assert_fnc=lambda x: x.data['results'][0],
)
def test_supplier_price_break_list(self):
"""Test the SupplierPriceBreak API list functionality."""

View File

@@ -315,12 +315,9 @@ class PurchaseOrderTest(OrderTest):
def test_output_options(self):
"""Test the various output options for the PurchaseOrder detail endpoint."""
url = reverse('api-po-detail', kwargs={'pk': 1})
response = self.get(url, {'supplier_detail': 'true'}, expected_code=200)
self.assertIn('supplier_detail', response.data)
response = self.get(url, {'supplier_detail': 'false'}, expected_code=200)
self.assertNotIn('supplier_detail', response.data)
self.run_output_test(
reverse('api-po-detail', kwargs={'pk': 1}), ['supplier_detail']
)
def test_po_operations(self):
"""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):
"""Test PurchaseOrderLineItem output option endpoint."""
url = reverse('api-po-line-detail', kwargs={'pk': 1})
response = self.get(url, {'part_detail': 'true'}, expected_code=200)
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)
self.run_output_test(
reverse('api-po-line-detail', kwargs={'pk': 1}),
['part_detail', 'order_detail'],
)
class PurchaseOrderDownloadTest(OrderTest):
@@ -1774,11 +1764,9 @@ class SalesOrderTest(OrderTest):
def test_output_options(self):
"""Test the output options for the SalesOrder detail endpoint."""
url = reverse('api-so-detail', kwargs={'pk': 1})
response = self.get(url, {'customer_detail': True}, expected_code=200)
self.assertIn('customer_detail', response.data)
response = self.get(url, {'customer_detail': False}, expected_code=200)
self.assertNotIn('customer_detail', response.data)
self.run_output_test(
reverse('api-so-detail', kwargs={'pk': 1}), ['customer_detail']
)
class SalesOrderLineItemTest(OrderTest):
@@ -1943,14 +1931,10 @@ class SalesOrderLineItemTest(OrderTest):
def test_output_options(self):
"""Test the various output options for the SalesOrderLineItem detail endpoint."""
url = reverse('api-so-line-detail', kwargs={'pk': 1})
options = ['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)
self.run_output_test(
reverse('api-so-line-detail', kwargs={'pk': 1}),
['part_detail', 'order_detail', 'customer_detail'],
)
class SalesOrderDownloadTest(OrderTest):
@@ -2296,20 +2280,17 @@ class SalesOrderAllocateTest(OrderTest):
def test_output_options(self):
"""Test the various output options for the SalesOrderAllocation detail endpoint."""
url = reverse('api-so-allocation-list')
options = [
'part_detail',
'item_detail',
'order_detail',
'location_detail',
'customer_detail',
]
for option in options:
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])
self.run_output_test(
reverse('api-so-allocation-list'),
[
'part_detail',
'item_detail',
'order_detail',
'location_detail',
'customer_detail',
],
assert_subset=True,
)
class ReturnOrderTests(InvenTreeAPITestCase):
@@ -2679,14 +2660,9 @@ class ReturnOrderTests(InvenTreeAPITestCase):
def test_output_options(self):
"""Test the various output options for the ReturnOrder detail endpoint."""
url = reverse('api-return-order-detail', kwargs={'pk': 1})
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)
self.run_output_test(
reverse('api-return-order-detail', kwargs={'pk': 1}), ['customer_detail']
)
class ReturnOrderLineItemTests(InvenTreeAPITestCase):
@@ -2749,17 +2725,10 @@ class ReturnOrderLineItemTests(InvenTreeAPITestCase):
def test_output_options(self):
"""Test output options for detail endpoint."""
url = reverse('api-return-order-line-detail', kwargs={'pk': 1})
options = ['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)
self.run_output_test(
reverse('api-return-order-line-detail', kwargs={'pk': 1}),
['part_detail', 'item_detail', 'order_detail'],
)
def test_update(self):
"""Test updating ReturnOrderLineItem."""

View File

@@ -2666,13 +2666,10 @@ class BomItemTest(InvenTreeAPITestCase):
def test_output_options(self):
"""Test that various output options work as expected."""
url = reverse('api-bom-item-detail', kwargs={'pk': 3})
options = ['can_build', 'part_detail', 'sub_part_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)
self.run_output_test(
reverse('api-bom-item-detail', kwargs={'pk': 3}),
['can_build', 'part_detail', 'sub_part_detail'],
)
def test_add_bom_item(self):
"""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):
"""Test output options."""
url = reverse('api-location-detail', kwargs={'pk': 1})
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)
self.run_output_test(
reverse('api-location-detail', kwargs={'pk': 1}), [('path_detail', 'path')]
)
def test_stock_location_structural(self):
"""Test the effectiveness of structural stock locations.
@@ -1529,33 +1525,16 @@ class StockItemTest(StockAPITestCase):
def test_output_options(self):
"""Test the output options for StockItemt detail."""
url = reverse('api-stock-detail', kwargs={'pk': 1})
# Test cases: (parameter_name, response_field_name)
test_cases = [
('part_detail', 'part_detail'),
('path_detail', 'location_path'),
('supplier_part_detail', 'supplier_part_detail'),
('location_detail', 'location_detail'),
('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'",
)
self.run_output_test(
reverse('api-stock-detail', kwargs={'pk': 1}),
[
'part_detail',
('path_detail', 'location_path'),
'supplier_part_detail',
'location_detail',
'tests',
],
)
def test_install(self):
"""Test that stock item can be installed into another item, via the API."""
@@ -2223,19 +2202,10 @@ class StockTestResultTest(StockAPITestCase):
def test_output_options(self):
"""Test output options for single item retrieval."""
url = reverse('api-stock-test-result-detail', kwargs={'pk': 1})
response = self.get(url, {'user_detail': 'true'}, expected_code=200)
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)
self.run_output_test(
reverse('api-stock-test-result-detail', kwargs={'pk': 1}),
['user_detail', 'template_detail'],
)
class StockTrackingTest(StockAPITestCase):
@@ -2334,23 +2304,13 @@ class StockTrackingTest(StockAPITestCase):
def test_output_options(self):
"""Test output options."""
url = self.get_url()
response = self.client.get(
url, {'item_detail': True, 'user_detail': True, 'limit': 2}
self.run_output_test(
self.get_url(),
['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):
"""Unit tests for the stock assignment API endpoint, where stock items are manually assigned to a customer."""