From 2afd39356af478042969f6e554be4e732cc23034 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 15 Jul 2022 11:57:27 +1000 Subject: [PATCH] Int migration fix (#3323) * Add fix for stock migration - Ensure the serial number is not too large when performing migration - Add unit test for data migration (cherry picked from commit 661fbf0e3dbdf6444d3d25b02d68ad229925d87c) * Add similar fixes for PO and SO migrations (cherry picked from commit bde23c130c879e7663091fba808bbd57c52ed8bf) * And similar fix for BuildOrder reference field (cherry picked from commit ca0f4e00310aed0551f8fad5c57f90fae2177f04) * Update unit tests for API plugin mixin class - API at previous target URL has changed - Simplier to use the github API as a test case (cherry picked from commit dfe3172b7d7e7c6910aff0e6248b5609570607a9) * Revert test database name (cherry picked from commit 53333c29c38ae393b1e31e764e08a1239839a594) * Override default URL behaviour for unit test (cherry picked from commit 2c12a695294c2785e82b7f469f79a7d1a5412e71) --- .../migrations/0032_auto_20211014_0632.py | 4 ++ .../migrations/0052_auto_20211014_0631.py | 8 +++ InvenTree/order/test_migrations.py | 22 ++++++ .../migrations/0069_auto_20211109_2347.py | 3 + InvenTree/stock/test_migrations.py | 69 +++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 InvenTree/stock/test_migrations.py diff --git a/InvenTree/build/migrations/0032_auto_20211014_0632.py b/InvenTree/build/migrations/0032_auto_20211014_0632.py index ea6d5c954d..1ae56af4e6 100644 --- a/InvenTree/build/migrations/0032_auto_20211014_0632.py +++ b/InvenTree/build/migrations/0032_auto_20211014_0632.py @@ -24,6 +24,10 @@ def build_refs(apps, schema_editor): except Exception: # pragma: no cover ref = 0 + # Clip integer value to ensure it does not overflow database field + if ref > 0x7fffffff: + ref = 0x7fffffff + build.reference_int = ref build.save() diff --git a/InvenTree/order/migrations/0052_auto_20211014_0631.py b/InvenTree/order/migrations/0052_auto_20211014_0631.py index 94a591d3d6..36e2d882ac 100644 --- a/InvenTree/order/migrations/0052_auto_20211014_0631.py +++ b/InvenTree/order/migrations/0052_auto_20211014_0631.py @@ -23,6 +23,10 @@ def build_refs(apps, schema_editor): except Exception: # pragma: no cover ref = 0 + # Clip integer value to ensure it does not overflow database field + if ref > 0x7fffffff: + ref = 0x7fffffff + order.reference_int = ref order.save() @@ -40,6 +44,10 @@ def build_refs(apps, schema_editor): except Exception: # pragma: no cover ref = 0 + # Clip integer value to ensure it does not overflow database field + if ref > 0x7fffffff: + ref = 0x7fffffff + order.reference_int = ref order.save() diff --git a/InvenTree/order/test_migrations.py b/InvenTree/order/test_migrations.py index 1734501a9c..a45f09fc48 100644 --- a/InvenTree/order/test_migrations.py +++ b/InvenTree/order/test_migrations.py @@ -49,6 +49,19 @@ class TestRefIntMigrations(MigratorTestCase): with self.assertRaises(AttributeError): print(sales_order.reference_int) + # Create orders with very large reference values + self.po_pk = PurchaseOrder.objects.create( + supplier=supplier, + reference='999999999999999999999999999999999', + description='Big reference field', + ).pk + + self.so_pk = SalesOrder.objects.create( + customer=supplier, + reference='999999999999999999999999999999999', + description='Big reference field', + ).pk + def test_ref_field(self): """Test that the 'reference_int' field has been created and is filled out correctly.""" PurchaseOrder = self.new_state.apps.get_model('order', 'purchaseorder') @@ -63,6 +76,15 @@ class TestRefIntMigrations(MigratorTestCase): self.assertEqual(po.reference_int, ii) self.assertEqual(so.reference_int, ii) + # Tests for orders with overly large reference values + po = PurchaseOrder.objects.get(pk=self.po_pk) + self.assertEqual(po.reference, '999999999999999999999999999999999') + self.assertEqual(po.reference_int, 0x7fffffff) + + so = SalesOrder.objects.get(pk=self.so_pk) + self.assertEqual(so.reference, '999999999999999999999999999999999') + self.assertEqual(so.reference_int, 0x7fffffff) + class TestShipmentMigration(MigratorTestCase): """Test data migration for the "SalesOrderShipment" model.""" diff --git a/InvenTree/stock/migrations/0069_auto_20211109_2347.py b/InvenTree/stock/migrations/0069_auto_20211109_2347.py index e4e8128f1c..2691b6305e 100644 --- a/InvenTree/stock/migrations/0069_auto_20211109_2347.py +++ b/InvenTree/stock/migrations/0069_auto_20211109_2347.py @@ -28,6 +28,9 @@ def update_serials(apps, schema_editor): except Exception: serial = 0 + # Ensure the integer value is not too large for the database field + if serial > 0x7fffffff: + serial = 0x7fffffff item.serial_int = serial item.save() diff --git a/InvenTree/stock/test_migrations.py b/InvenTree/stock/test_migrations.py new file mode 100644 index 0000000000..07fa9f2fc7 --- /dev/null +++ b/InvenTree/stock/test_migrations.py @@ -0,0 +1,69 @@ +"""Unit tests for data migrations in the 'stock' app""" + +from django_test_migrations.contrib.unittest_case import MigratorTestCase + +from InvenTree import helpers + + +class TestSerialNumberMigration(MigratorTestCase): + """Test data migration which updates serial numbers""" + + migrate_from = ('stock', '0067_alter_stockitem_part') + migrate_to = ('stock', helpers.getNewestMigrationFile('stock')) + + def prepare(self): + """Create initial data for this migration""" + + Part = self.old_state.apps.get_model('part', 'part') + StockItem = self.old_state.apps.get_model('stock', 'stockitem') + + # Create a base part + my_part = Part.objects.create( + name='PART-123', + description='Some part', + active=True, + trackable=True, + level=0, + tree_id=0, + lft=0, rght=0 + ) + + # Create some serialized stock items + for sn in range(10, 20): + StockItem.objects.create( + part=my_part, + quantity=1, + serial=sn, + level=0, + tree_id=0, + lft=0, rght=0 + ) + + # Create a stock item with a very large serial number + item = StockItem.objects.create( + part=my_part, + quantity=1, + serial='9999999999999999999999999999999999999999999999999999999999999', + level=0, + tree_id=0, + lft=0, rght=0 + ) + + self.big_ref_pk = item.pk + + def test_migrations(self): + """Test that the migrations have been applied correctly""" + + StockItem = self.new_state.apps.get_model('stock', 'stockitem') + + # Check that the serial number integer conversion has been applied correctly + for sn in range(10, 20): + item = StockItem.objects.get(serial_int=sn) + + self.assertEqual(item.serial, str(sn)) + + big_ref_item = StockItem.objects.get(pk=self.big_ref_pk) + + # Check that the StockItem maximum serial number + self.assertEqual(big_ref_item.serial, '9999999999999999999999999999999999999999999999999999999999999') + self.assertEqual(big_ref_item.serial_int, 0x7fffffff)