mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Unit test speed improvements (#4463)
* Unit test speed improvements - Move from insantiating data in setUp to setUpTestData * Update UserMixin class for API testing * Bunch of test updates * Further test updates * Test fixes * Add allowances for exchange rate server not responding * Fixes for group role test
This commit is contained in:
parent
9c594ed52b
commit
2dfea9b825
@ -33,56 +33,69 @@ class UserMixin:
|
|||||||
# Set list of roles automatically associated with the user
|
# Set list of roles automatically associated with the user
|
||||||
roles = []
|
roles = []
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
"""Setup for all tests."""
|
def setUpTestData(cls):
|
||||||
super().setUp()
|
"""Run setup for all tests in a given class"""
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
# Create a user to log in with
|
# Create a user to log in with
|
||||||
self.user = get_user_model().objects.create_user(
|
cls.user = get_user_model().objects.create_user(
|
||||||
username=self.username,
|
username=cls.username,
|
||||||
password=self.password,
|
password=cls.password,
|
||||||
email=self.email
|
email=cls.email
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a group for the user
|
# Create a group for the user
|
||||||
self.group = Group.objects.create(name='my_test_group')
|
cls.group = Group.objects.create(name='my_test_group')
|
||||||
self.user.groups.add(self.group)
|
cls.user.groups.add(cls.group)
|
||||||
|
|
||||||
if self.superuser:
|
if cls.superuser:
|
||||||
self.user.is_superuser = True
|
cls.user.is_superuser = True
|
||||||
|
|
||||||
if self.is_staff:
|
if cls.is_staff:
|
||||||
self.user.is_staff = True
|
cls.user.is_staff = True
|
||||||
|
|
||||||
self.user.save()
|
cls.user.save()
|
||||||
|
|
||||||
# Assign all roles if set
|
# Assign all roles if set
|
||||||
if self.roles == 'all':
|
if cls.roles == 'all':
|
||||||
self.assignRole(assign_all=True)
|
cls.assignRole(group=cls.group, assign_all=True)
|
||||||
|
|
||||||
# else filter the roles
|
# else filter the roles
|
||||||
else:
|
else:
|
||||||
for role in self.roles:
|
for role in cls.roles:
|
||||||
self.assignRole(role)
|
cls.assignRole(role=role, group=cls.group)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Run setup for individual test methods"""
|
||||||
|
|
||||||
if self.auto_login:
|
if self.auto_login:
|
||||||
self.client.login(username=self.username, password=self.password)
|
self.client.login(username=self.username, password=self.password)
|
||||||
|
|
||||||
def assignRole(self, role=None, assign_all: bool = False):
|
@classmethod
|
||||||
|
def assignRole(cls, role=None, assign_all: bool = False, group=None):
|
||||||
"""Set the user roles for the registered user.
|
"""Set the user roles for the registered user.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
role: Role of the format 'rule.permission' e.g. 'part.add'
|
role: Role of the format 'rule.permission' e.g. 'part.add'
|
||||||
assign_all: Set to True to assign *all* roles
|
assign_all: Set to True to assign *all* roles
|
||||||
|
group: The group to assign roles to (or leave None to use the group assigned to this class)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if group is None:
|
||||||
|
group = cls.group
|
||||||
|
|
||||||
if type(assign_all) is not bool:
|
if type(assign_all) is not bool:
|
||||||
# Raise exception if common mistake is made!
|
# Raise exception if common mistake is made!
|
||||||
raise TypeError('assign_all must be a boolean value')
|
raise TypeError('assignRole: assign_all must be a boolean value')
|
||||||
|
|
||||||
|
if not role and not assign_all:
|
||||||
|
raise ValueError('assignRole: either role must be provided, or assign_all must be set')
|
||||||
|
|
||||||
if not assign_all and role:
|
if not assign_all and role:
|
||||||
rule, perm = role.split('.')
|
rule, perm = role.split('.')
|
||||||
|
|
||||||
for ruleset in self.group.rule_sets.all():
|
for ruleset in group.rule_sets.all():
|
||||||
|
|
||||||
if assign_all or ruleset.name == rule:
|
if assign_all or ruleset.name == rule:
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from rest_framework import status
|
|||||||
|
|
||||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||||
from InvenTree.helpers import InvenTreeTestCase
|
from InvenTree.helpers import InvenTreeTestCase
|
||||||
from users.models import RuleSet
|
from users.models import RuleSet, update_group_roles
|
||||||
|
|
||||||
|
|
||||||
class HTMLAPITests(InvenTreeTestCase):
|
class HTMLAPITests(InvenTreeTestCase):
|
||||||
@ -126,6 +126,10 @@ class APITests(InvenTreeAPITestCase):
|
|||||||
"""
|
"""
|
||||||
url = reverse('api-user-roles')
|
url = reverse('api-user-roles')
|
||||||
|
|
||||||
|
# Delete all rules
|
||||||
|
self.group.rule_sets.all().delete()
|
||||||
|
update_group_roles(self.group)
|
||||||
|
|
||||||
response = self.client.get(url, format='json')
|
response = self.client.get(url, format='json')
|
||||||
|
|
||||||
# Not logged in, so cannot access user role data
|
# Not logged in, so cannot access user role data
|
||||||
|
@ -390,10 +390,10 @@ class TestMPTT(TestCase):
|
|||||||
'location',
|
'location',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Setup for all tests."""
|
"""Setup for all tests."""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
StockLocation.objects.rebuild()
|
StockLocation.objects.rebuild()
|
||||||
|
|
||||||
def test_self_as_parent(self):
|
def test_self_as_parent(self):
|
||||||
|
@ -731,38 +731,42 @@ class BuildOverallocationTest(BuildAPITest):
|
|||||||
Using same Build ID=1 as allocation test above.
|
Using same Build ID=1 as allocation test above.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Basic operation as part of test suite setup"""
|
"""Basic operation as part of test suite setup"""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
self.assignRole('build.add')
|
cls.assignRole('build.add')
|
||||||
self.assignRole('build.change')
|
cls.assignRole('build.change')
|
||||||
|
|
||||||
self.build = Build.objects.get(pk=1)
|
cls.build = Build.objects.get(pk=1)
|
||||||
self.url = reverse('api-build-finish', kwargs={'pk': self.build.pk})
|
cls.url = reverse('api-build-finish', kwargs={'pk': cls.build.pk})
|
||||||
|
|
||||||
StockItem.objects.create(part=Part.objects.get(pk=50), quantity=30)
|
StockItem.objects.create(part=Part.objects.get(pk=50), quantity=30)
|
||||||
|
|
||||||
# Keep some state for use in later assertions, and then overallocate
|
# Keep some state for use in later assertions, and then overallocate
|
||||||
self.state = {}
|
cls.state = {}
|
||||||
self.allocation = {}
|
cls.allocation = {}
|
||||||
for i, bi in enumerate(self.build.part.bom_items.all()):
|
|
||||||
rq = self.build.required_quantity(bi, None) + i + 1
|
for i, bi in enumerate(cls.build.part.bom_items.all()):
|
||||||
|
rq = cls.build.required_quantity(bi, None) + i + 1
|
||||||
si = StockItem.objects.filter(part=bi.sub_part, quantity__gte=rq).first()
|
si = StockItem.objects.filter(part=bi.sub_part, quantity__gte=rq).first()
|
||||||
|
|
||||||
self.state[bi.sub_part] = (si, si.quantity, rq)
|
cls.state[bi.sub_part] = (si, si.quantity, rq)
|
||||||
BuildItem.objects.create(
|
BuildItem.objects.create(
|
||||||
build=self.build,
|
build=cls.build,
|
||||||
stock_item=si,
|
stock_item=si,
|
||||||
quantity=rq,
|
quantity=rq,
|
||||||
)
|
)
|
||||||
|
|
||||||
# create and complete outputs
|
# create and complete outputs
|
||||||
self.build.create_build_output(self.build.quantity)
|
cls.build.create_build_output(cls.build.quantity)
|
||||||
outputs = self.build.build_outputs.all()
|
outputs = cls.build.build_outputs.all()
|
||||||
self.build.complete_build_output(outputs[0], self.user)
|
cls.build.complete_build_output(outputs[0], cls.user)
|
||||||
|
|
||||||
|
def test_setup(self):
|
||||||
|
"""Validate expected state after set-up."""
|
||||||
|
|
||||||
# Validate expected state after set-up.
|
|
||||||
self.assertEqual(self.build.incomplete_outputs.count(), 0)
|
self.assertEqual(self.build.incomplete_outputs.count(), 0)
|
||||||
self.assertEqual(self.build.complete_outputs.count(), 1)
|
self.assertEqual(self.build.complete_outputs.count(), 1)
|
||||||
self.assertEqual(self.build.completed, self.build.quantity)
|
self.assertEqual(self.build.completed, self.build.quantity)
|
||||||
|
@ -29,7 +29,8 @@ class BuildTestBase(TestCase):
|
|||||||
'users',
|
'users',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Initialize data to use for these tests.
|
"""Initialize data to use for these tests.
|
||||||
|
|
||||||
The base Part 'assembly' has a BOM consisting of three parts:
|
The base Part 'assembly' has a BOM consisting of three parts:
|
||||||
@ -45,27 +46,29 @@ class BuildTestBase(TestCase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
# Create a base "Part"
|
# Create a base "Part"
|
||||||
self.assembly = Part.objects.create(
|
cls.assembly = Part.objects.create(
|
||||||
name="An assembled part",
|
name="An assembled part",
|
||||||
description="Why does it matter what my description is?",
|
description="Why does it matter what my description is?",
|
||||||
assembly=True,
|
assembly=True,
|
||||||
trackable=True,
|
trackable=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.sub_part_1 = Part.objects.create(
|
cls.sub_part_1 = Part.objects.create(
|
||||||
name="Widget A",
|
name="Widget A",
|
||||||
description="A widget",
|
description="A widget",
|
||||||
component=True
|
component=True
|
||||||
)
|
)
|
||||||
|
|
||||||
self.sub_part_2 = Part.objects.create(
|
cls.sub_part_2 = Part.objects.create(
|
||||||
name="Widget B",
|
name="Widget B",
|
||||||
description="A widget",
|
description="A widget",
|
||||||
component=True
|
component=True
|
||||||
)
|
)
|
||||||
|
|
||||||
self.sub_part_3 = Part.objects.create(
|
cls.sub_part_3 = Part.objects.create(
|
||||||
name="Widget C",
|
name="Widget C",
|
||||||
description="A widget",
|
description="A widget",
|
||||||
component=True,
|
component=True,
|
||||||
@ -73,63 +76,63 @@ class BuildTestBase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Create BOM item links for the parts
|
# Create BOM item links for the parts
|
||||||
self.bom_item_1 = BomItem.objects.create(
|
cls.bom_item_1 = BomItem.objects.create(
|
||||||
part=self.assembly,
|
part=cls.assembly,
|
||||||
sub_part=self.sub_part_1,
|
sub_part=cls.sub_part_1,
|
||||||
quantity=5
|
quantity=5
|
||||||
)
|
)
|
||||||
|
|
||||||
self.bom_item_2 = BomItem.objects.create(
|
cls.bom_item_2 = BomItem.objects.create(
|
||||||
part=self.assembly,
|
part=cls.assembly,
|
||||||
sub_part=self.sub_part_2,
|
sub_part=cls.sub_part_2,
|
||||||
quantity=3,
|
quantity=3,
|
||||||
optional=True
|
optional=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# sub_part_3 is trackable!
|
# sub_part_3 is trackable!
|
||||||
self.bom_item_3 = BomItem.objects.create(
|
cls.bom_item_3 = BomItem.objects.create(
|
||||||
part=self.assembly,
|
part=cls.assembly,
|
||||||
sub_part=self.sub_part_3,
|
sub_part=cls.sub_part_3,
|
||||||
quantity=2
|
quantity=2
|
||||||
)
|
)
|
||||||
|
|
||||||
ref = generate_next_build_reference()
|
ref = generate_next_build_reference()
|
||||||
|
|
||||||
# Create a "Build" object to make 10x objects
|
# Create a "Build" object to make 10x objects
|
||||||
self.build = Build.objects.create(
|
cls.build = Build.objects.create(
|
||||||
reference=ref,
|
reference=ref,
|
||||||
title="This is a build",
|
title="This is a build",
|
||||||
part=self.assembly,
|
part=cls.assembly,
|
||||||
quantity=10,
|
quantity=10,
|
||||||
issued_by=get_user_model().objects.get(pk=1),
|
issued_by=get_user_model().objects.get(pk=1),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create some build output (StockItem) objects
|
# Create some build output (StockItem) objects
|
||||||
self.output_1 = StockItem.objects.create(
|
cls.output_1 = StockItem.objects.create(
|
||||||
part=self.assembly,
|
part=cls.assembly,
|
||||||
quantity=3,
|
quantity=3,
|
||||||
is_building=True,
|
is_building=True,
|
||||||
build=self.build
|
build=cls.build
|
||||||
)
|
)
|
||||||
|
|
||||||
self.output_2 = StockItem.objects.create(
|
cls.output_2 = StockItem.objects.create(
|
||||||
part=self.assembly,
|
part=cls.assembly,
|
||||||
quantity=7,
|
quantity=7,
|
||||||
is_building=True,
|
is_building=True,
|
||||||
build=self.build,
|
build=cls.build,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create some stock items to assign to the build
|
# Create some stock items to assign to the build
|
||||||
self.stock_1_1 = StockItem.objects.create(part=self.sub_part_1, quantity=3)
|
cls.stock_1_1 = StockItem.objects.create(part=cls.sub_part_1, quantity=3)
|
||||||
self.stock_1_2 = StockItem.objects.create(part=self.sub_part_1, quantity=100)
|
cls.stock_1_2 = StockItem.objects.create(part=cls.sub_part_1, quantity=100)
|
||||||
|
|
||||||
self.stock_2_1 = StockItem.objects.create(part=self.sub_part_2, quantity=5)
|
cls.stock_2_1 = StockItem.objects.create(part=cls.sub_part_2, quantity=5)
|
||||||
self.stock_2_2 = StockItem.objects.create(part=self.sub_part_2, quantity=5)
|
cls.stock_2_2 = StockItem.objects.create(part=cls.sub_part_2, quantity=5)
|
||||||
self.stock_2_3 = StockItem.objects.create(part=self.sub_part_2, quantity=5)
|
cls.stock_2_3 = StockItem.objects.create(part=cls.sub_part_2, quantity=5)
|
||||||
self.stock_2_4 = StockItem.objects.create(part=self.sub_part_2, quantity=5)
|
cls.stock_2_4 = StockItem.objects.create(part=cls.sub_part_2, quantity=5)
|
||||||
self.stock_2_5 = StockItem.objects.create(part=self.sub_part_2, quantity=5)
|
cls.stock_2_5 = StockItem.objects.create(part=cls.sub_part_2, quantity=5)
|
||||||
|
|
||||||
self.stock_3_1 = StockItem.objects.create(part=self.sub_part_3, quantity=1000)
|
cls.stock_3_1 = StockItem.objects.create(part=cls.sub_part_3, quantity=1000)
|
||||||
|
|
||||||
|
|
||||||
class BuildTest(BuildTestBase):
|
class BuildTest(BuildTestBase):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Tests for mechanisms in common."""
|
"""Tests for mechanisms in common."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
@ -921,7 +922,16 @@ class CurrencyAPITests(InvenTreeAPITestCase):
|
|||||||
# Delete any existing exchange rate data
|
# Delete any existing exchange rate data
|
||||||
Rate.objects.all().delete()
|
Rate.objects.all().delete()
|
||||||
|
|
||||||
self.post(reverse('api-currency-refresh'))
|
# Updating via the external exchange may not work every time
|
||||||
|
for _idx in range(5):
|
||||||
|
self.post(reverse('api-currency-refresh'))
|
||||||
|
|
||||||
# There should be some new exchange rate objects now
|
# There should be some new exchange rate objects now
|
||||||
self.assertTrue(Rate.objects.all().exists())
|
if Rate.objects.all().exists():
|
||||||
|
# Exit early
|
||||||
|
return
|
||||||
|
|
||||||
|
# Delay and try again
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
raise TimeoutError("Could not refresh currency exchange data after 5 attempts")
|
||||||
|
@ -17,11 +17,14 @@ class CompanyTest(InvenTreeAPITestCase):
|
|||||||
'purchase_order.change',
|
'purchase_order.change',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Perform initialization for the unit test class"""
|
"""Perform initialization for the unit test class"""
|
||||||
super().setUp()
|
|
||||||
|
|
||||||
self.acme = Company.objects.create(name='ACME', description='Supplier', is_customer=False, is_supplier=True)
|
super().setUpTestData()
|
||||||
|
|
||||||
|
# Create some company objects to work with
|
||||||
|
cls.acme = Company.objects.create(name='ACME', description='Supplier', is_customer=False, is_supplier=True)
|
||||||
Company.objects.create(name='Drippy Cup Co.', description='Customer', is_customer=True, is_supplier=False)
|
Company.objects.create(name='Drippy Cup Co.', description='Customer', is_customer=True, is_supplier=False)
|
||||||
Company.objects.create(name='Sippy Cup Emporium', description='Another supplier')
|
Company.objects.create(name='Sippy Cup Emporium', description='Another supplier')
|
||||||
|
|
||||||
|
@ -26,8 +26,12 @@ class CompanySimpleTest(TestCase):
|
|||||||
'price_breaks',
|
'price_breaks',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Perform initialization for the tests in this class"""
|
"""Perform initialization for the tests in this class"""
|
||||||
|
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
Company.objects.create(name='ABC Co.',
|
Company.objects.create(name='ABC Co.',
|
||||||
description='Seller of ABC products',
|
description='Seller of ABC products',
|
||||||
website='www.abc-sales.com',
|
website='www.abc-sales.com',
|
||||||
@ -35,10 +39,10 @@ class CompanySimpleTest(TestCase):
|
|||||||
is_customer=False,
|
is_customer=False,
|
||||||
is_supplier=True)
|
is_supplier=True)
|
||||||
|
|
||||||
self.acme0001 = SupplierPart.objects.get(SKU='ACME0001')
|
cls.acme0001 = SupplierPart.objects.get(SKU='ACME0001')
|
||||||
self.acme0002 = SupplierPart.objects.get(SKU='ACME0002')
|
cls.acme0002 = SupplierPart.objects.get(SKU='ACME0002')
|
||||||
self.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS')
|
cls.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS')
|
||||||
self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312')
|
cls.zergm312 = SupplierPart.objects.get(SKU='ZERGM312')
|
||||||
|
|
||||||
def test_company_model(self):
|
def test_company_model(self):
|
||||||
"""Tests for the company model data"""
|
"""Tests for the company model data"""
|
||||||
|
@ -27,9 +27,10 @@ class LabelTest(InvenTreeAPITestCase):
|
|||||||
'stock'
|
'stock'
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self) -> None:
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Ensure that some label instances exist as part of init routine"""
|
"""Ensure that some label instances exist as part of init routine"""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
apps.get_app_config('label').create_labels()
|
apps.get_app_config('label').create_labels()
|
||||||
|
|
||||||
def test_default_labels(self):
|
def test_default_labels(self):
|
||||||
|
@ -1408,25 +1408,33 @@ class SalesOrderLineItemTest(OrderTest):
|
|||||||
|
|
||||||
LIST_URL = reverse('api-so-line-list')
|
LIST_URL = reverse('api-so-line-list')
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Init routine for this unit test class"""
|
"""Init routine for this unit test class"""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
# List of salable parts
|
# List of salable parts
|
||||||
parts = Part.objects.filter(salable=True)
|
parts = Part.objects.filter(salable=True)
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
|
||||||
# Create a bunch of SalesOrderLineItems for each order
|
# Create a bunch of SalesOrderLineItems for each order
|
||||||
for idx, so in enumerate(models.SalesOrder.objects.all()):
|
for idx, so in enumerate(models.SalesOrder.objects.all()):
|
||||||
|
|
||||||
for part in parts:
|
for part in parts:
|
||||||
models.SalesOrderLineItem.objects.create(
|
lines.append(
|
||||||
order=so,
|
models.SalesOrderLineItem(
|
||||||
part=part,
|
order=so,
|
||||||
quantity=(idx + 1) * 5,
|
part=part,
|
||||||
reference=f"Order {so.reference} - line {idx}",
|
quantity=(idx + 1) * 5,
|
||||||
|
reference=f"Order {so.reference} - line {idx}",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.url = reverse('api-so-line-list')
|
# Bulk create
|
||||||
|
models.SalesOrderLineItem.objects.bulk_create(lines)
|
||||||
|
|
||||||
|
cls.url = reverse('api-so-line-list')
|
||||||
|
|
||||||
def test_so_line_list(self):
|
def test_so_line_list(self):
|
||||||
"""Test list endpoint"""
|
"""Test list endpoint"""
|
||||||
|
@ -25,33 +25,34 @@ class SalesOrderTest(TestCase):
|
|||||||
'users',
|
'users',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Initial setup for this set of unit tests"""
|
"""Initial setup for this set of unit tests"""
|
||||||
# Create a Company to ship the goods to
|
# Create a Company to ship the goods to
|
||||||
self.customer = Company.objects.create(name="ABC Co", description="My customer", is_customer=True)
|
cls.customer = Company.objects.create(name="ABC Co", description="My customer", is_customer=True)
|
||||||
|
|
||||||
# Create a Part to ship
|
# Create a Part to ship
|
||||||
self.part = Part.objects.create(name='Spanner', salable=True, description='A spanner that I sell')
|
cls.part = Part.objects.create(name='Spanner', salable=True, description='A spanner that I sell')
|
||||||
|
|
||||||
# Create some stock!
|
# Create some stock!
|
||||||
self.Sa = StockItem.objects.create(part=self.part, quantity=100)
|
cls.Sa = StockItem.objects.create(part=cls.part, quantity=100)
|
||||||
self.Sb = StockItem.objects.create(part=self.part, quantity=200)
|
cls.Sb = StockItem.objects.create(part=cls.part, quantity=200)
|
||||||
|
|
||||||
# Create a SalesOrder to ship against
|
# Create a SalesOrder to ship against
|
||||||
self.order = SalesOrder.objects.create(
|
cls.order = SalesOrder.objects.create(
|
||||||
customer=self.customer,
|
customer=cls.customer,
|
||||||
reference='SO-1234',
|
reference='SO-1234',
|
||||||
customer_reference='ABC 55555'
|
customer_reference='ABC 55555'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a Shipment against this SalesOrder
|
# Create a Shipment against this SalesOrder
|
||||||
self.shipment = SalesOrderShipment.objects.create(
|
cls.shipment = SalesOrderShipment.objects.create(
|
||||||
order=self.order,
|
order=cls.order,
|
||||||
reference='SO-001',
|
reference='SO-001',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a line item
|
# Create a line item
|
||||||
self.line = SalesOrderLineItem.objects.create(quantity=50, order=self.order, part=self.part)
|
cls.line = SalesOrderLineItem.objects.create(quantity=50, order=cls.order, part=cls.part)
|
||||||
|
|
||||||
def test_so_reference(self):
|
def test_so_reference(self):
|
||||||
"""Unit tests for sales order generation"""
|
"""Unit tests for sales order generation"""
|
||||||
|
@ -2357,12 +2357,12 @@ class PartPricing(common.models.MetaMixin):
|
|||||||
|
|
||||||
if self.scheduled_for_update:
|
if self.scheduled_for_update:
|
||||||
# Ignore if the pricing is already scheduled to be updated
|
# Ignore if the pricing is already scheduled to be updated
|
||||||
logger.info(f"Pricing for {p} already scheduled for update - skipping")
|
logger.debug(f"Pricing for {p} already scheduled for update - skipping")
|
||||||
return
|
return
|
||||||
|
|
||||||
if counter > 25:
|
if counter > 25:
|
||||||
# Prevent infinite recursion / stack depth issues
|
# Prevent infinite recursion / stack depth issues
|
||||||
logger.info(counter, f"Skipping pricing update for {p} - maximum depth exceeded")
|
logger.debug(counter, f"Skipping pricing update for {p} - maximum depth exceeded")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -1893,15 +1893,16 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
|
|||||||
'part.change',
|
'part.change',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Create test data as part of setup routine"""
|
"""Create test data as part of setup routine"""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
# Ensure the part "variant" tree is correctly structured
|
# Ensure the part "variant" tree is correctly structured
|
||||||
Part.objects.rebuild()
|
Part.objects.rebuild()
|
||||||
|
|
||||||
# Add a new part
|
# Add a new part
|
||||||
self.part = Part.objects.create(
|
cls.part = Part.objects.create(
|
||||||
name='Banana',
|
name='Banana',
|
||||||
description='This is a banana',
|
description='This is a banana',
|
||||||
category=PartCategory.objects.get(pk=1),
|
category=PartCategory.objects.get(pk=1),
|
||||||
@ -1910,12 +1911,12 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
|
|||||||
# Create some stock items associated with the part
|
# Create some stock items associated with the part
|
||||||
|
|
||||||
# First create 600 units which are OK
|
# First create 600 units which are OK
|
||||||
StockItem.objects.create(part=self.part, quantity=100)
|
StockItem.objects.create(part=cls.part, quantity=100)
|
||||||
StockItem.objects.create(part=self.part, quantity=200)
|
StockItem.objects.create(part=cls.part, quantity=200)
|
||||||
StockItem.objects.create(part=self.part, quantity=300)
|
StockItem.objects.create(part=cls.part, quantity=300)
|
||||||
|
|
||||||
# Now create another 400 units which are LOST
|
# Now create another 400 units which are LOST
|
||||||
StockItem.objects.create(part=self.part, quantity=400, status=StockStatus.LOST)
|
StockItem.objects.create(part=cls.part, quantity=400, status=StockStatus.LOST)
|
||||||
|
|
||||||
def get_part_data(self):
|
def get_part_data(self):
|
||||||
"""Helper function for retrieving part data"""
|
"""Helper function for retrieving part data"""
|
||||||
|
@ -17,26 +17,35 @@ class BomUploadTest(InvenTreeAPITestCase):
|
|||||||
'part.change',
|
'part.change',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Create BOM data as part of setup routine"""
|
"""Create BOM data as part of setup routine"""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
self.part = Part.objects.create(
|
cls.part = Part.objects.create(
|
||||||
name='Assembly',
|
name='Assembly',
|
||||||
description='An assembled part',
|
description='An assembled part',
|
||||||
assembly=True,
|
assembly=True,
|
||||||
component=False,
|
component=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
Part.objects.create(
|
parts.append(
|
||||||
name=f"Component {i}",
|
Part(
|
||||||
IPN=f"CMP_{i}",
|
name=f"Component {i}",
|
||||||
description="A subcomponent that can be used in a BOM",
|
IPN=f"CMP_{i}",
|
||||||
component=True,
|
description="A subcomponent that can be used in a BOM",
|
||||||
assembly=False,
|
component=True,
|
||||||
|
assembly=False,
|
||||||
|
lft=0, rght=0,
|
||||||
|
level=0, tree_id=0,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Part.objects.bulk_create(parts)
|
||||||
|
|
||||||
def post_bom(self, filename, file_data, clear_existing=None, expected_code=None, content_type='text/plain'):
|
def post_bom(self, filename, file_data, clear_existing=None, expected_code=None, content_type='text/plain'):
|
||||||
"""Helper function for submitting a BOM file"""
|
"""Helper function for submitting a BOM file"""
|
||||||
bom_file = SimpleUploadedFile(
|
bom_file = SimpleUploadedFile(
|
||||||
|
@ -19,15 +19,19 @@ class CategoryTest(TestCase):
|
|||||||
'params',
|
'params',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Extract some interesting categories for time-saving"""
|
"""Extract some interesting categories for time-saving"""
|
||||||
self.electronics = PartCategory.objects.get(name='Electronics')
|
|
||||||
self.mechanical = PartCategory.objects.get(name='Mechanical')
|
super().setUpTestData()
|
||||||
self.resistors = PartCategory.objects.get(name='Resistors')
|
|
||||||
self.capacitors = PartCategory.objects.get(name='Capacitors')
|
cls.electronics = PartCategory.objects.get(name='Electronics')
|
||||||
self.fasteners = PartCategory.objects.get(name='Fasteners')
|
cls.mechanical = PartCategory.objects.get(name='Mechanical')
|
||||||
self.ic = PartCategory.objects.get(name='IC')
|
cls.resistors = PartCategory.objects.get(name='Resistors')
|
||||||
self.transceivers = PartCategory.objects.get(name='Transceivers')
|
cls.capacitors = PartCategory.objects.get(name='Capacitors')
|
||||||
|
cls.fasteners = PartCategory.objects.get(name='Fasteners')
|
||||||
|
cls.ic = PartCategory.objects.get(name='IC')
|
||||||
|
cls.transceivers = PartCategory.objects.get(name='Transceivers')
|
||||||
|
|
||||||
def test_parents(self):
|
def test_parents(self):
|
||||||
"""Test that the parent fields are properly set, based on the test fixtures."""
|
"""Test that the parent fields are properly set, based on the test fixtures."""
|
||||||
|
@ -134,14 +134,16 @@ class PartTest(TestCase):
|
|||||||
'part_pricebreaks'
|
'part_pricebreaks'
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Create some Part instances as part of init routine"""
|
"""Create some Part instances as part of init routine"""
|
||||||
super().setUp()
|
|
||||||
|
|
||||||
self.r1 = Part.objects.get(name='R_2K2_0805')
|
super().setUpTestData()
|
||||||
self.r2 = Part.objects.get(name='R_4K7_0603')
|
|
||||||
|
|
||||||
self.c1 = Part.objects.get(name='C_22N_0805')
|
cls.r1 = Part.objects.get(name='R_2K2_0805')
|
||||||
|
cls.r2 = Part.objects.get(name='R_4K7_0603')
|
||||||
|
|
||||||
|
cls.c1 = Part.objects.get(name='C_22N_0805')
|
||||||
|
|
||||||
Part.objects.rebuild()
|
Part.objects.rebuild()
|
||||||
|
|
||||||
@ -529,15 +531,16 @@ class PartSubscriptionTests(InvenTreeTestCase):
|
|||||||
'part',
|
'part',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Create category and part data as part of setup routine"""
|
"""Create category and part data as part of setup routine"""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
# electronics / IC / MCU
|
# Electronics / IC / MCU
|
||||||
self.category = PartCategory.objects.get(pk=4)
|
cls.category = PartCategory.objects.get(pk=4)
|
||||||
|
|
||||||
self.part = Part.objects.create(
|
cls.part = Part.objects.create(
|
||||||
category=self.category,
|
category=cls.category,
|
||||||
name='STM32F103',
|
name='STM32F103',
|
||||||
description='Currently worth a lot of money',
|
description='Currently worth a lot of money',
|
||||||
is_template=True,
|
is_template=True,
|
||||||
@ -629,14 +632,16 @@ class BaseNotificationIntegrationTest(InvenTreeTestCase):
|
|||||||
'stock'
|
'stock'
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Add an email address as part of initialization"""
|
"""Add an email address as part of initialization"""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
# Add Mailadress
|
|
||||||
EmailAddress.objects.create(user=self.user, email='test@testing.com')
|
# Add email address
|
||||||
|
EmailAddress.objects.create(user=cls.user, email='test@testing.com')
|
||||||
|
|
||||||
# Define part that will be tested
|
# Define part that will be tested
|
||||||
self.part = Part.objects.get(name='R_2K2_0805')
|
cls.part = Part.objects.get(name='R_2K2_0805')
|
||||||
|
|
||||||
def _notification_run(self, run_class=None):
|
def _notification_run(self, run_class=None):
|
||||||
"""Run a notification test suit through.
|
"""Run a notification test suit through.
|
||||||
|
@ -20,6 +20,8 @@ class PartPricingTests(InvenTreeTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup routines"""
|
"""Setup routines"""
|
||||||
|
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
self.generate_exchange_rates()
|
self.generate_exchange_rates()
|
||||||
|
|
||||||
# Create a new part for performing pricing calculations
|
# Create a new part for performing pricing calculations
|
||||||
@ -29,8 +31,6 @@ class PartPricingTests(InvenTreeTestCase):
|
|||||||
assembly=True
|
assembly=True
|
||||||
)
|
)
|
||||||
|
|
||||||
return super().setUp()
|
|
||||||
|
|
||||||
def create_price_breaks(self):
|
def create_price_breaks(self):
|
||||||
"""Create some price breaks for the part, in various currencies"""
|
"""Create some price breaks for the part, in various currencies"""
|
||||||
|
|
||||||
|
@ -66,25 +66,29 @@ class PluginTagTests(TestCase):
|
|||||||
class InvenTreePluginTests(TestCase):
|
class InvenTreePluginTests(TestCase):
|
||||||
"""Tests for InvenTreePlugin."""
|
"""Tests for InvenTreePlugin."""
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Setup for all tests."""
|
"""Setup for all tests."""
|
||||||
self.plugin = InvenTreePlugin()
|
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
|
cls.plugin = InvenTreePlugin()
|
||||||
|
|
||||||
class NamedPlugin(InvenTreePlugin):
|
class NamedPlugin(InvenTreePlugin):
|
||||||
"""a named plugin."""
|
"""a named plugin."""
|
||||||
NAME = 'abc123'
|
NAME = 'abc123'
|
||||||
|
|
||||||
self.named_plugin = NamedPlugin()
|
cls.named_plugin = NamedPlugin()
|
||||||
|
|
||||||
class SimpleInvenTreePlugin(InvenTreePlugin):
|
class SimpleInvenTreePlugin(InvenTreePlugin):
|
||||||
NAME = 'SimplePlugin'
|
NAME = 'SimplePlugin'
|
||||||
|
|
||||||
self.plugin_simple = SimpleInvenTreePlugin()
|
cls.plugin_simple = SimpleInvenTreePlugin()
|
||||||
|
|
||||||
class OldInvenTreePlugin(InvenTreePlugin):
|
class OldInvenTreePlugin(InvenTreePlugin):
|
||||||
PLUGIN_SLUG = 'old'
|
PLUGIN_SLUG = 'old'
|
||||||
|
|
||||||
self.plugin_old = OldInvenTreePlugin()
|
cls.plugin_old = OldInvenTreePlugin()
|
||||||
|
|
||||||
class NameInvenTreePlugin(InvenTreePlugin):
|
class NameInvenTreePlugin(InvenTreePlugin):
|
||||||
NAME = 'Aplugin'
|
NAME = 'Aplugin'
|
||||||
@ -97,8 +101,8 @@ class InvenTreePluginTests(TestCase):
|
|||||||
WEBSITE = 'https://aa.bb/cc'
|
WEBSITE = 'https://aa.bb/cc'
|
||||||
LICENSE = 'MIT'
|
LICENSE = 'MIT'
|
||||||
|
|
||||||
self.plugin_name = NameInvenTreePlugin()
|
cls.plugin_name = NameInvenTreePlugin()
|
||||||
self.plugin_sample = SampleIntegrationPlugin()
|
cls.plugin_sample = SampleIntegrationPlugin()
|
||||||
|
|
||||||
class VersionInvenTreePlugin(InvenTreePlugin):
|
class VersionInvenTreePlugin(InvenTreePlugin):
|
||||||
NAME = 'Version'
|
NAME = 'Version'
|
||||||
@ -106,7 +110,7 @@ class InvenTreePluginTests(TestCase):
|
|||||||
MIN_VERSION = '0.1.0'
|
MIN_VERSION = '0.1.0'
|
||||||
MAX_VERSION = '0.1.3'
|
MAX_VERSION = '0.1.3'
|
||||||
|
|
||||||
self.plugin_version = VersionInvenTreePlugin()
|
cls.plugin_version = VersionInvenTreePlugin()
|
||||||
|
|
||||||
def test_basic_plugin_init(self):
|
def test_basic_plugin_init(self):
|
||||||
"""Check if a basic plugin intis."""
|
"""Check if a basic plugin intis."""
|
||||||
|
@ -50,9 +50,10 @@ class StockLocationTest(StockAPITestCase):
|
|||||||
|
|
||||||
list_url = reverse('api-location-list')
|
list_url = reverse('api-location-list')
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Setup for all tests."""
|
"""Setup for all tests."""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
# Add some stock locations
|
# Add some stock locations
|
||||||
StockLocation.objects.create(name='top', description='top category')
|
StockLocation.objects.create(name='top', description='top category')
|
||||||
@ -1413,30 +1414,32 @@ class StockMergeTest(StockAPITestCase):
|
|||||||
|
|
||||||
URL = reverse('api-stock-merge')
|
URL = reverse('api-stock-merge')
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Setup for all tests."""
|
"""Setup for all tests."""
|
||||||
super().setUp()
|
|
||||||
|
|
||||||
self.part = part.models.Part.objects.get(pk=25)
|
super().setUpTestData()
|
||||||
self.loc = StockLocation.objects.get(pk=1)
|
|
||||||
self.sp_1 = company.models.SupplierPart.objects.get(pk=100)
|
|
||||||
self.sp_2 = company.models.SupplierPart.objects.get(pk=101)
|
|
||||||
|
|
||||||
self.item_1 = StockItem.objects.create(
|
cls.part = part.models.Part.objects.get(pk=25)
|
||||||
part=self.part,
|
cls.loc = StockLocation.objects.get(pk=1)
|
||||||
supplier_part=self.sp_1,
|
cls.sp_1 = company.models.SupplierPart.objects.get(pk=100)
|
||||||
|
cls.sp_2 = company.models.SupplierPart.objects.get(pk=101)
|
||||||
|
|
||||||
|
cls.item_1 = StockItem.objects.create(
|
||||||
|
part=cls.part,
|
||||||
|
supplier_part=cls.sp_1,
|
||||||
quantity=100,
|
quantity=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.item_2 = StockItem.objects.create(
|
cls.item_2 = StockItem.objects.create(
|
||||||
part=self.part,
|
part=cls.part,
|
||||||
supplier_part=self.sp_2,
|
supplier_part=cls.sp_2,
|
||||||
quantity=100,
|
quantity=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.item_3 = StockItem.objects.create(
|
cls.item_3 = StockItem.objects.create(
|
||||||
part=self.part,
|
part=cls.part,
|
||||||
supplier_part=self.sp_2,
|
supplier_part=cls.sp_2,
|
||||||
quantity=50,
|
quantity=50,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,19 +30,20 @@ class StockTestBase(InvenTreeTestCase):
|
|||||||
'stock_tests',
|
'stock_tests',
|
||||||
]
|
]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
"""Setup for all tests."""
|
"""Setup for all tests."""
|
||||||
super().setUp()
|
super().setUpTestData()
|
||||||
|
|
||||||
# Extract some shortcuts from the fixtures
|
# Extract some shortcuts from the fixtures
|
||||||
self.home = StockLocation.objects.get(name='Home')
|
cls.home = StockLocation.objects.get(name='Home')
|
||||||
self.bathroom = StockLocation.objects.get(name='Bathroom')
|
cls.bathroom = StockLocation.objects.get(name='Bathroom')
|
||||||
self.diningroom = StockLocation.objects.get(name='Dining Room')
|
cls.diningroom = StockLocation.objects.get(name='Dining Room')
|
||||||
|
|
||||||
self.office = StockLocation.objects.get(name='Office')
|
cls.office = StockLocation.objects.get(name='Office')
|
||||||
self.drawer1 = StockLocation.objects.get(name='Drawer_1')
|
cls.drawer1 = StockLocation.objects.get(name='Drawer_1')
|
||||||
self.drawer2 = StockLocation.objects.get(name='Drawer_2')
|
cls.drawer2 = StockLocation.objects.get(name='Drawer_2')
|
||||||
self.drawer3 = StockLocation.objects.get(name='Drawer_3')
|
cls.drawer3 = StockLocation.objects.get(name='Drawer_3')
|
||||||
|
|
||||||
# Ensure the MPTT objects are correctly rebuild
|
# Ensure the MPTT objects are correctly rebuild
|
||||||
Part.objects.rebuild()
|
Part.objects.rebuild()
|
||||||
|
Loading…
Reference in New Issue
Block a user