From 9160182727c309309425f018f5984a664d208f83 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 28 Aug 2026 10:41:22 +0530 Subject: [PATCH] test: improve ERPNext test isolation and runtime (#58507) --- .../bank_clearance/test_bank_clearance.py | 26 +- .../bank_transaction/test_bank_transaction.py | 43 +- .../doctype/finance_book/test_finance_book.py | 9 +- .../journal_entry/test_journal_entry.py | 5 +- .../payment_entry/test_payment_entry.py | 4 - .../test_payment_reconciliation.py | 11 - .../payment_request/test_payment_request.py | 38 +- .../test_pos_closing_entry.py | 60 +-- .../doctype/pos_invoice/test_pos_invoice.py | 54 +-- .../pos_invoice/test_pos_invoice_merge.py | 12 - .../purchase_invoice/test_purchase_invoice.py | 20 - .../sales_invoice/test_sales_invoice.py | 53 +-- .../doctype/subscription/test_subscription.py | 19 +- .../doctype/tax_rule/test_tax_rule.py | 19 - .../test_accounts_receivable.py | 17 +- .../test_accounts_receivable_summary.py | 11 +- .../general_ledger/test_general_ledger.py | 13 +- .../report/gross_profit/test_gross_profit.py | 18 +- .../share_balance/test_share_balance.py | 9 +- .../report/share_ledger/test_share_ledger.py | 17 +- erpnext/assets/doctype/asset/test_asset.py | 6 +- .../services/gl_composer.py | 6 +- .../test_asset_capitalization.py | 6 +- .../doctype/asset_repair/test_asset_repair.py | 2 - .../purchase_order/test_purchase_order.py | 23 +- .../test_supplier_quotation.py | 8 +- .../tests/test_accounts_controller.py | 2 +- .../tests/test_sales_and_purchase_return.py | 24 +- .../tests/test_selling_controller.py | 13 +- .../tests/test_stock_controller.py | 17 +- .../code_list/test_code_list_import.py | 109 +++-- erpnext/hooks.py | 2 + .../test_maintenance_schedule.py | 19 - .../test_maintenance_visit.py | 14 +- .../production_plan/test_production_plan.py | 6 - .../doctype/routing/test_routing.py | 46 ++- .../doctype/work_order/test_work_order.py | 14 +- .../test_production_planning_report.py | 15 +- .../test_quality_inspection_summary.py | 2 - .../scheduling/test_plan_adapter.py | 2 - .../activity_cost/test_activity_cost.py | 9 +- .../project_update/test_project_update.py | 1 - .../doctype/timesheet/test_timesheet.py | 8 +- .../selling/doctype/customer/test_customer.py | 56 +-- .../proforma_invoice/test_proforma_invoice.py | 56 ++- .../doctype/quotation/test_quotation.py | 14 +- .../doctype/sales_order/test_sales_order.py | 12 +- .../lost_quotations/test_lost_quotations.py | 23 +- erpnext/setup/demo.py | 9 +- .../test_authorization_control.py | 20 +- erpnext/setup/doctype/company/test_company.py | 12 +- erpnext/stock/doctype/bin/test_bin.py | 5 +- .../test_company_restriction.py | 52 ++- .../delivery_note/test_delivery_note.py | 8 +- .../delivery_trip/test_delivery_trip.py | 2 +- .../test_inventory_dimension.py | 17 +- erpnext/stock/doctype/item/test_item.py | 107 ++--- .../item_attribute/test_item_attribute.py | 1 - .../test_landed_cost_voucher.py | 37 +- .../doctype/packed_item/test_packed_item.py | 1 - .../purchase_receipt/test_purchase_receipt.py | 32 +- .../test_quality_inspection.py | 3 - .../test_repost_item_valuation.py | 41 +- .../test_serial_and_batch_bundle.py | 2 - .../test_stock_ledger_entry.py | 25 +- .../test_stock_reposting_settings.py | 4 - .../test_stock_reservation_entry.py | 29 +- .../test_available_batch_report.py | 12 - .../test_serial_and_batch_summary.py | 11 - .../stock_balance/test_stock_balance.py | 3 +- erpnext/stock/tests/test_stock_ledger.py | 14 +- erpnext/support/doctype/issue/test_issue.py | 1 - .../test_service_level_agreement.py | 15 +- erpnext/templates/pages/test_partners.py | 19 +- erpnext/tests/assertions.py | 24 ++ erpnext/tests/bootstrap_test_data.py | 7 +- erpnext/tests/test_utils.py | 88 ++++ erpnext/tests/utils.py | 389 +++++++++--------- 78 files changed, 779 insertions(+), 1184 deletions(-) create mode 100644 erpnext/tests/assertions.py create mode 100644 erpnext/tests/test_utils.py diff --git a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py index 1190ffac9f3..90e5b3fd6c7 100644 --- a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py @@ -4,29 +4,18 @@ import frappe from frappe.utils import add_months, getdate -from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import ( set_default_account_for_mode_of_payment, ) from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.stock.doctype.item.test_item import create_item -from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.tests.utils import ERPNextTestSuite, if_lending_app_installed, if_lending_app_not_installed class TestBankClearance(ERPNextTestSuite): def setUp(self): frappe.clear_cache() - create_warehouse( - warehouse_name="_Test Warehouse", - properties={"parent_warehouse": "All Warehouses - _TC"}, - company="_Test Company", - ) - create_item("_Test Item") - create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company") - make_bank_account() add_transactions() @@ -139,11 +128,8 @@ def add_transactions(): def make_payment_entry(): - from erpnext.buying.doctype.supplier.test_supplier import create_supplier - - supplier = create_supplier(supplier_name="_Test Supplier") pi = make_purchase_invoice( - supplier=supplier.name, + supplier="_Test Supplier", supplier_warehouse="_Test Warehouse - _TC", expense_account="Cost of Goods Sold - _TC", uom="Nos", @@ -158,10 +144,6 @@ def make_payment_entry(): def make_pos_sales_invoice(): - from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( - make_customer, - ) - mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"}) if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}): @@ -170,13 +152,13 @@ def make_pos_sales_invoice(): ) mode_of_payment.save() - customer = make_customer(customer="_Test Customer") - mode_of_payment = frappe.get_doc("Mode of Payment", "Wire Transfer") set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank Clearance - _TC") - si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1) + si = create_sales_invoice( + customer="_Test Customer", item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1 + ) si.set("payments", []) si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 1000}) si.insert() diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 63493101abe..bdda939cf94 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -23,8 +23,6 @@ from erpnext.tests.utils import ERPNextTestSuite, if_lending_app_installed class TestBankTransaction(ERPNextTestSuite): def setUp(self): - make_pos_profile() - # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error uniq_identifier = frappe.generate_hash(length=10) gl_account = create_gl_account("_Test Bank " + uniq_identifier) @@ -32,6 +30,15 @@ class TestBankTransaction(ERPNextTestSuite): gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier ) + if self._testMethodName in { + "test_cancel_voucher", + "test_clearance_date_cleared_on_amend", + "test_reconcile", + }: + add_reconciliation_data(bank_account, gl_account) + return + + make_pos_profile() add_transactions(bank_account=bank_account) add_vouchers(gl_account=gl_account) @@ -47,7 +54,7 @@ class TestBankTransaction(ERPNextTestSuite): from_date=bank_transaction.date, to_date=utils.today(), ) - self.assertEqual(linked_payments[0]["party"], "Conrad Electronic") + self.assertIn("Conrad Electronic", [payment["party"] for payment in linked_payments]) # This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment def test_reconcile(self): @@ -347,6 +354,36 @@ def add_transactions(bank_account="_Test Bank - _TC"): doc.submit() +def add_reconciliation_data(bank_account, gl_account): + doc = frappe.get_doc( + { + "doctype": "Bank Transaction", + "description": "1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G", + "date": "2018-10-23", + "deposit": 1700, + "currency": "INR", + "bank_account": bank_account, + } + ).insert() + doc.submit() + + frappe.get_doc( + { + "doctype": "Supplier", + "supplier_group": "All Supplier Groups", + "supplier_type": "Company", + "supplier_name": "Mr G", + } + ).insert(ignore_if_duplicate=True) + + pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) + pe.reference_no = "Herr G Nov 18" + pe.reference_date = "2018-11-01" + pe.insert() + pe.submit() + + def add_vouchers(gl_account="_Test Bank - _TC"): try: frappe.get_doc( diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py index d9d6c0e44ab..14c88573051 100644 --- a/erpnext/accounts/doctype/finance_book/test_finance_book.py +++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py @@ -31,11 +31,4 @@ class TestFinanceBook(ERPNextTestSuite): def create_finance_book(): - if not frappe.db.exists("Finance Book", "_Test Finance Book"): - finance_book = frappe.get_doc( - {"doctype": "Finance Book", "finance_book_name": "_Test Finance Book"} - ).insert() - else: - finance_book = frappe.get_doc("Finance Book", "_Test Finance Book") - - return finance_book + return frappe.get_doc("Finance Book", "Test Finance Book 1") diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 9257e41d8e9..8c58868aff2 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -318,9 +318,8 @@ class TestJournalEntry(ERPNextTestSuite): ) # the guard must not disclose the reversal to a user who cannot read the entry - frappe.set_user("Guest") - self.addCleanup(frappe.set_user, "Administrator") - self.assertRaises(frappe.PermissionError, make_reverse_journal_entry, rjv.name) + with self.set_user("Guest"): + self.assertRaises(frappe.PermissionError, make_reverse_journal_entry, rjv.name) def test_disallow_change_in_account_currency_for_a_party(self): # create jv in USD diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index c9e5405e90f..179bbcec97f 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -789,7 +789,6 @@ class TestPaymentEntry(ERPNextTestSuite): company="_Test Company", ) frappe.db.set_value("Company", "_Test Company", "bank_charges_account", bank_charges_account) - self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "bank_charges_account", "") pe = frappe.new_doc("Payment Entry") pe.payment_type = "Internal Transfer" @@ -834,7 +833,6 @@ class TestPaymentEntry(ERPNextTestSuite): company="_Test Company", ) frappe.db.set_value("Company", "_Test Company", "bank_charges_account", bank_charges_account) - self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "bank_charges_account", "") pe = frappe.new_doc("Payment Entry") pe.payment_type = "Internal Transfer" @@ -1109,8 +1107,6 @@ class TestPaymentEntry(ERPNextTestSuite): ) frappe.db.set_value("Company", "_Test Company", "exchange_gain_account", gain_account) frappe.db.set_value("Company", "_Test Company", "exchange_loss_account", loss_account) - self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "exchange_gain_account", "") - self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "exchange_loss_account", "") si_gain = create_sales_invoice( customer="_Test Customer USD", diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index bec6ab1e236..3d359a1e8fd 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -201,8 +201,6 @@ class TestPaymentReconciliation(ERPNextTestSuite): ) frappe.db.set_value("Company", self.company, "exchange_gain_account", gain_account) frappe.db.set_value("Company", self.company, "exchange_loss_account", loss_account) - self.addCleanup(frappe.db.set_value, "Company", self.company, "exchange_gain_account", "") - self.addCleanup(frappe.db.set_value, "Company", self.company, "exchange_loss_account", "") return gain_account, loss_account def create_foreign_currency_sales_invoice(self, conversion_rate): @@ -1331,15 +1329,6 @@ class TestPaymentReconciliation(ERPNextTestSuite): test_user = "test@example.com" permitted_ccs = ["_Test Cost Center - _TC", "_Test Cost Center 2 - _TC"] restricted_cc = "_Test Write Off Cost Center - _TC" - existing_apply_strict_user_permissions = cint( - frappe.db.get_single_value("System Settings", "apply_strict_user_permissions") - ) - self.addCleanup( - frappe.db.set_single_value, - "System Settings", - "apply_strict_user_permissions", - existing_apply_strict_user_permissions, - ) transaction_date = nowdate() rate = 100 diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 51bb1c0ce98..d76cfe6c138 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -29,6 +29,9 @@ from erpnext.stock.doctype.item.test_item import make_item from erpnext.tests.utils import ERPNextTestSuite PAYMENT_URL = "https://example.com/payment" +SEND_EMAIL_MOCK = MagicMock(return_value=None) +GET_PAYMENT_URL_MOCK = MagicMock(return_value=PAYMENT_URL) +GET_PAYMENT_GATEWAY_CONTROLLER_MOCK = MagicMock() payment_gateways = [ {"doctype": "Payment Gateway", "gateway": "_Test Gateway"}, @@ -71,6 +74,18 @@ payment_method = [ ] +@patch( + "erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.send_email", + new=SEND_EMAIL_MOCK, +) +@patch( + "erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.get_payment_url", + new=GET_PAYMENT_URL_MOCK, +) +@patch( + "erpnext.accounts.doctype.payment_request.payment_request._get_payment_gateway_controller", + new=GET_PAYMENT_GATEWAY_CONTROLLER_MOCK, +) class TestPaymentRequest(ERPNextTestSuite): def setUp(self): for payment_gateway in payment_gateways: @@ -89,24 +104,11 @@ class TestPaymentRequest(ERPNextTestSuite): ): frappe.get_doc(method).insert(ignore_permissions=True) - send_email = patch( - "erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.send_email", - return_value=None, - ) - self.send_email = send_email.start() - self.addCleanup(send_email.stop) - get_payment_url = patch( - # this also shadows one (1) call to _get_payment_gateway_controller - "erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.get_payment_url", - return_value=PAYMENT_URL, - ) - self.get_payment_url = get_payment_url.start() - self.addCleanup(get_payment_url.stop) - _get_payment_gateway_controller = patch( - "erpnext.accounts.doctype.payment_request.payment_request._get_payment_gateway_controller", - ) - self._get_payment_gateway_controller = _get_payment_gateway_controller.start() - self.addCleanup(_get_payment_gateway_controller.stop) + for mock in (SEND_EMAIL_MOCK, GET_PAYMENT_URL_MOCK, GET_PAYMENT_GATEWAY_CONTROLLER_MOCK): + mock.reset_mock() + self.send_email = SEND_EMAIL_MOCK + self.get_payment_url = GET_PAYMENT_URL_MOCK + self._get_payment_gateway_controller = GET_PAYMENT_GATEWAY_CONTROLLER_MOCK def test_payment_request_linkings(self): so_inr = make_sales_order(currency="INR", do_not_save=True) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index bcee69b64ba..1ea5ac4a5cf 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -21,13 +21,12 @@ from erpnext.tests.utils import ERPNextTestSuite class TestPOSClosingEntry(ERPNextTestSuite): def setUp(self): - init_user_and_profile() + self.test_user, self.pos_profile = init_user_and_profile() make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100) frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") def test_pos_closing_entry(self): - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) + opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) @@ -59,8 +58,7 @@ class TestPOSClosingEntry(ERPNextTestSuite): """ Test if POS Closing Entry is created without item code """ - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) + opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) pos_inv = create_pos_invoice(rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) @@ -79,10 +77,9 @@ class TestPOSClosingEntry(ERPNextTestSuite): """ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) + opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) - test_item_qty = get_test_item_qty(pos_profile) + test_item_qty = get_test_item_qty(self.pos_profile) pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) @@ -104,13 +101,11 @@ class TestPOSClosingEntry(ERPNextTestSuite): pcv_doc.flags.in_test = True pcv_doc.submit() - opening_entry = create_opening_entry(pos_profile, test_user.name) - test_item_qty_after_sales = get_test_item_qty(pos_profile) + test_item_qty_after_sales = get_test_item_qty(self.pos_profile) self.assertEqual(test_item_qty_after_sales, test_item_qty - 1) def test_cancelling_of_pos_closing_entry(self): - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) + opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) @@ -169,9 +164,7 @@ class TestPOSClosingEntry(ERPNextTestSuite): pos_profile.insert() self.assertTrue(frappe.db.exists("POS Profile", pos_profile.name)) - test_user = init_user_and_profile(do_not_create_pos_profile=1) - - opening_entry = create_opening_entry(pos_profile, test_user.name) + opening_entry = create_opening_entry(pos_profile, self.test_user.name) pos_inv1 = create_pos_invoice(rate=350, do_not_submit=1, pos_profile=pos_profile.name) pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) pos_inv1.save() @@ -195,9 +188,6 @@ class TestPOSClosingEntry(ERPNextTestSuite): def test_merging_into_sales_invoice_for_batched_item(self): frappe.flags.print_message = False - from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( - init_user_and_profile, - ) from erpnext.stock.doctype.batch.batch import get_batch_qty item_doc = make_item( @@ -220,8 +210,7 @@ class TestPOSClosingEntry(ERPNextTestSuite): ) batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) + opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) pos_inv = create_pos_invoice( item_code=item_code, @@ -291,18 +280,17 @@ class TestPOSClosingEntry(ERPNextTestSuite): @ERPNextTestSuite.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}) def test_closing_entries_with_sales_invoice(self): - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) + opening_entry = create_opening_entry(self.pos_profile, self.test_user.name) pos_si = create_sales_invoice( - qty=10, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1 + qty=10, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=1 ) pos_si.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000}) pos_si.save() pos_si.submit() pos_si2 = create_sales_invoice( - qty=5, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=11 + qty=5, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=11 ) pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000}) pos_si2.save() @@ -332,12 +320,10 @@ class TestPOSClosingEntry(ERPNextTestSuite): """ from erpnext.accounts.doctype.sales_invoice.mapper import make_sales_return - test_user, pos_profile = init_user_and_profile() - with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}): - opening_entry1 = create_opening_entry(pos_profile, test_user.name) + opening_entry1 = create_opening_entry(self.pos_profile, self.test_user.name) - pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile) + pos_si1, pos_si2 = create_multiple_sales_invoices(self.pos_profile) pos_inv = create_pos_invoice(rate=100, do_not_save=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100}) @@ -357,13 +343,13 @@ class TestPOSClosingEntry(ERPNextTestSuite): self.assertEqual(pos_si2.pos_closing_entry, pcv_doc1.name) with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}): - opening_entry2 = create_opening_entry(pos_profile, test_user.name) + opening_entry2 = create_opening_entry(self.pos_profile, self.test_user.name) - pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile) + pos_inv1, pos_inv2 = create_multiple_pos_invoices(self.pos_profile) # Trying to create Sales Invoice when invoice_type is set to POS Invoice. pos_si3 = create_sales_invoice( - qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1 + qty=1, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=1 ) pos_si3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100}) self.assertRaises(frappe.ValidationError, pos_si3.save) @@ -394,16 +380,14 @@ class TestPOSClosingEntry(ERPNextTestSuite): """ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return - test_user, pos_profile = init_user_and_profile() - with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}): - opening_entry1 = create_opening_entry(pos_profile, test_user.name) + opening_entry1 = create_opening_entry(self.pos_profile, self.test_user.name) - pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile) + pos_inv1, pos_inv2 = create_multiple_pos_invoices(self.pos_profile) # Trying to create Sales Invoice when invoice_type is set to POS Invoice. pos_sinv = create_sales_invoice( - qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1 + qty=1, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=1 ) pos_sinv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100}) self.assertRaises(frappe.ValidationError, pos_sinv.save) @@ -421,9 +405,9 @@ class TestPOSClosingEntry(ERPNextTestSuite): self.assertEqual(pcv_doc1.grand_total, 300) with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}): - opening_entry2 = create_opening_entry(pos_profile, test_user.name) + opening_entry2 = create_opening_entry(self.pos_profile, self.test_user.name) - pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile) + pos_si1, pos_si2 = create_multiple_sales_invoices(self.pos_profile) pos_inv3 = create_pos_invoice(rate=100, do_not_save=1) pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100}) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index f8026afcba6..e6bd02692cf 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -4,6 +4,7 @@ import copy import frappe from frappe import _ +from frappe.utils import add_to_date from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import ( set_default_account_for_mode_of_payment, @@ -53,14 +54,14 @@ class TestPOSInvoice(POSInvoiceTestMixin): w2 = frappe.get_doc(w.doctype, w.name) - import time - - time.sleep(1) w.save() - - import time - - time.sleep(1) + frappe.db.set_value( + w.doctype, + w.name, + "modified", + add_to_date(w.modified, seconds=1), + update_modified=False, + ) self.assertRaises(frappe.TimestampMismatchError, w2.save) def test_change_naming_series(self): @@ -902,9 +903,6 @@ class TestPOSInvoice(POSInvoiceTestMixin): self.assertEqual(pos_inv.items[0].rate, 300) def test_delivered_serial_no_case(self): - from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import ( - init_user_and_profile, - ) from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item @@ -916,8 +914,6 @@ class TestPOSInvoice(POSInvoiceTestMixin): self.assertEqual(serial_no, delivered_serial_no) - init_user_and_profile() - pos_inv = create_pos_invoice( item_code="_Test Serialized Item With Series", serial_no=[serial_no], @@ -931,13 +927,9 @@ class TestPOSInvoice(POSInvoiceTestMixin): def test_bundle_stock_availability_validation(self): from erpnext.accounts.doctype.pos_invoice.pos_invoice import ProductBundleStockValidationError - from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import ( - init_user_and_profile, - ) from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle from erpnext.stock.doctype.item.test_item import create_item - - init_user_and_profile() + from erpnext.stock.utils import get_stock_balance frappe.set_user("Administrator") @@ -959,9 +951,18 @@ class TestPOSInvoice(POSInvoiceTestMixin): is_stock_item=1, ) - # Add initial stock: SubA=5, SubB=2 - make_stock_entry(item_code=sub_item_a, target=warehouse, qty=5, company=company) - make_stock_entry(item_code=sub_item_b, target=warehouse, qty=2, company=company) + # Set initial stock to SubA=5 and SubB=2, even when this test is rerun on the same site. + for item_code, target_qty in ((sub_item_a, 5), (sub_item_b, 2)): + balance = get_stock_balance(item_code, warehouse) + difference = target_qty - balance + if difference: + make_stock_entry( + item_code=item_code, + to_warehouse=warehouse if difference > 0 else None, + from_warehouse=warehouse if difference < 0 else None, + qty=abs(difference), + company=company, + ) # Create Product Bundle: Test Bundle (SubA x2 + SubB x1) bundle_item = "_Test Bundle" @@ -1010,16 +1011,19 @@ class TestPOSInvoice(POSInvoiceTestMixin): def create_pos_invoice(**args): args = frappe._dict(args) - pos_profile = None - if not args.pos_profile: - pos_profile = make_pos_profile() - pos_profile.save() + pos_profile_name = args.pos_profile + if not pos_profile_name: + pos_profile_name = frappe.db.exists("POS Profile", "_Test POS Profile") + if not pos_profile_name: + pos_profile = make_pos_profile() + pos_profile.save() + pos_profile_name = pos_profile.name pos_inv = frappe.new_doc("POS Invoice") pos_inv.update(args) pos_inv.update_stock = 1 pos_inv.is_pos = 1 - pos_inv.pos_profile = args.pos_profile or pos_profile.name + pos_inv.pos_profile = pos_profile_name if args.posting_date: pos_inv.set_posting_time = 1 diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice_merge.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice_merge.py index d79169c34a9..f8e10ef8da2 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice_merge.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice_merge.py @@ -26,14 +26,10 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin): from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( make_closing_entry_from_opening, ) - from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( - init_user_and_profile, - ) from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( consolidate_pos_invoices, ) - test_user, pos_profile = init_user_and_profile() pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270}) pos_inv.save() @@ -55,14 +51,10 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin): from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( make_closing_entry_from_opening, ) - from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( - init_user_and_profile, - ) from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( consolidate_pos_invoices, ) - test_user, pos_profile = init_user_and_profile() pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300}) pos_inv.append( @@ -107,9 +99,6 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin): from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( make_closing_entry_from_opening, ) - from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( - init_user_and_profile, - ) from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( consolidate_pos_invoices, ) @@ -121,7 +110,6 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin): make_item(item, {"is_stock_item": 1}) make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300) - test_user, pos_profile = init_user_and_profile() pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1) pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300}) pos_inv.append( diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 04d83e07c32..78e270ebb70 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -578,17 +578,7 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): make_purchase_invoice as create_purchase_invoice, ) - original_value = frappe.db.get_single_value( - "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate" - ) - frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) - self.addCleanup( - frappe.db.set_single_value, - "Buying Settings", - "set_landed_cost_based_on_purchase_invoice_rate", - original_value, - ) pr = make_purchase_receipt( company="_Test Company with perpetual inventory", @@ -616,16 +606,7 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): make_purchase_invoice as create_purchase_invoice, ) - original_value = frappe.db.get_single_value( - "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate" - ) frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) - self.addCleanup( - frappe.db.set_single_value, - "Buying Settings", - "set_landed_cost_based_on_purchase_invoice_rate", - original_value, - ) pr = frappe.new_doc("Purchase Receipt") pr.currency = "USD" @@ -3545,7 +3526,6 @@ def make_purchase_invoice_against_cost_center(**args): def setup_provisional_accounting(**args): args = frappe._dict(args) - create_item("_Test Non Stock Item", is_stock_item=0) company = args.company or "_Test Company" provisional_account = create_account( account_name=args.account_name or "Provision Account", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ef70269196f..a6ddf05c45e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -7,7 +7,7 @@ import json import frappe from frappe import qb from frappe.model.dynamic_links import get_dynamic_link_map -from frappe.utils import add_days, cint, flt, format_date, getdate, nowdate, today +from frappe.utils import add_days, add_to_date, cint, flt, format_date, getdate, nowdate, today import erpnext from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account @@ -129,14 +129,14 @@ class TestSalesInvoice(ERPNextTestSuite): w2 = frappe.get_doc(w.doctype, w.name) - import time - - time.sleep(1) w.save() - - import time - - time.sleep(1) + frappe.db.set_value( + w.doctype, + w.name, + "modified", + add_to_date(w.modified, seconds=1), + update_modified=False, + ) self.assertRaises(frappe.TimestampMismatchError, w2.save) def test_sales_invoice_change_naming_series(self): @@ -3817,25 +3817,12 @@ class TestSalesInvoice(ERPNextTestSuite): # enable common party accounting frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) - # create a dimension and make it mandatory - if not frappe.get_all("Accounting Dimension", filters={"document_type": "Department"}): - dim = frappe.get_doc( - { - "doctype": "Accounting Dimension", - "document_type": "Department", - "dimension_defaults": [{"company": "_Test Company", "mandatory_for_bs": True}], - } - ) - dim.save() - else: - dim = frappe.get_doc( - "Accounting Dimension", - frappe.get_all("Accounting Dimension", filters={"document_type": "Department"})[0], - ) - dim.disabled = False - dim.dimension_defaults = [] - dim.append("dimension_defaults", {"company": "_Test Company", "mandatory_for_bs": True}) - dim.save() + # make the shared department dimension mandatory + dim = frappe.get_doc("Accounting Dimension", {"document_type": "Department"}) + dim.disabled = False + dim.dimension_defaults = [] + dim.append("dimension_defaults", {"company": "_Test Company", "mandatory_for_bs": True}) + dim.save() # create a sales invoice si = create_sales_invoice( @@ -5789,12 +5776,6 @@ def create_internal_parties(): allowed_to_interact_with="Wind Power LLC", ) - create_internal_customer( - customer_name="_Test Internal Customer 2", - represents_company="_Test Company with perpetual inventory", - allowed_to_interact_with="_Test Company with perpetual inventory", - ) - create_internal_customer( customer_name="_Test Internal Customer 3", represents_company="_Test Company", @@ -5815,12 +5796,6 @@ def create_internal_parties(): allowed_to_interact_with="_Test Company 1", ) - create_internal_supplier( - supplier_name="_Test Internal Supplier 2", - represents_company="_Test Company with perpetual inventory", - allowed_to_interact_with="_Test Company with perpetual inventory", - ) - create_internal_supplier( supplier_name="_Test Internal Customer 3", represents_company="_Test Company", diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index ef96f5f150f..2b5ee9362d8 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -1,6 +1,7 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt +from unittest.mock import patch import frappe from frappe.utils.data import ( @@ -658,23 +659,15 @@ class TestSubscription(ERPNextTestSuite): sub2 = create_subscription(start_date="2018-01-02") processed = [] - original_process = Subscription.process - original_rollback = frappe.db.rollback def patched(self, posting_date=None): processed.append(self.name) if self.name == sub1.name: raise frappe.ValidationError("forced failure") - Subscription.process = patched - # process_all calls frappe.db.rollback() on error which would otherwise wipe - # the test transaction; stub it so we can observe the iteration in isolation. - frappe.db.rollback = lambda *a, **kw: None - try: + # Stub transaction recovery so the test can observe the complete iteration in isolation. + with patch.object(Subscription, "process", patched), patch.object(frappe.db, "rollback"): process_all([sub1.name, sub2.name]) - finally: - Subscription.process = original_process - frappe.db.rollback = original_rollback self.assertEqual(processed, [sub1.name, sub2.name]) @@ -1073,12 +1066,6 @@ def create_plan(**kwargs): def create_parties(): - if not frappe.db.exists("Supplier", "_Test Supplier"): - supplier = frappe.new_doc("Supplier") - supplier.supplier_name = "_Test Supplier" - supplier.supplier_group = "All Supplier Groups" - supplier.insert() - if not frappe.db.exists("Customer", "_Test Subscription Customer"): customer = frappe.new_doc("Customer") customer.customer_name = "_Test Subscription Customer" diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index f14bf4563a6..0fdfcc0e805 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -63,25 +63,6 @@ class TestTaxRule(ERPNextTestSuite): def test_for_parent_supplier_group(self): purchase_template = "_Test Purchase Taxes and Charges Template - _TC" - if not frappe.db.exists("Purchase Taxes and Charges Template", purchase_template): - frappe.get_doc( - { - "doctype": "Purchase Taxes and Charges Template", - "title": "_Test Purchase Taxes and Charges Template", - "company": "_Test Company", - "taxes": [ - { - "account_head": "_Test Account VAT - _TC", - "charge_type": "On Net Total", - "description": "VAT", - "doctype": "Purchase Taxes and Charges", - "cost_center": "Main - _TC", - "rate": 6, - } - ], - } - ).insert() - make_tax_rule( supplier_group="All Supplier Groups", tax_type="Purchase", diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 09d3ba47192..56f1139e2a2 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -869,9 +869,7 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): self.assertEqual(rows_b[0].future_amount, 50.0) def test_sales_person(self): - sales_person = frappe.get_doc( - {"doctype": "Sales Person", "sales_person_name": "John Clark", "enabled": True} - ).insert() + sales_person = frappe.get_doc("Sales Person", "_Test Sales Person") si = self.create_sales_invoice(do_not_submit=True) si.append("sales_team", {"sales_person": sales_person.name, "allocated_percentage": 100}) si.save().submit() @@ -1494,17 +1492,8 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): def test_receivable_filtered_by_sales_partner(self): frappe.set_user("Administrator") - partner_a, partner_b = "_Test AR Sales Partner A", "_Test AR Sales Partner B" - for partner in (partner_a, partner_b): - if not frappe.db.exists("Sales Partner", partner): - frappe.get_doc( - { - "doctype": "Sales Partner", - "partner_name": partner, - "commission_rate": 0, - "territory": "All Territories", - } - ).insert() + partner_a = "_Test Sales Partner India - 1" + partner_b = "_Test Sales Partner India - 2" def _si(sales_partner): si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True, qty=2) diff --git a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py index 80b98a6d6bd..e5633194a54 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py @@ -193,16 +193,7 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): self.assertEqual(len(rpt_output), 0) def test_03_summary_sales_partner_column(self): - partner = "_Test AR Summary Sales Partner" - if not frappe.db.exists("Sales Partner", partner): - frappe.get_doc( - { - "doctype": "Sales Partner", - "partner_name": partner, - "commission_rate": 0, - "territory": "All Territories", - } - ).insert() + partner = "_Test Sales Partner India - 1" si = create_sales_invoice( item=self.item, diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index c35785bee4f..8225426556d 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -21,10 +21,8 @@ class TestGeneralLedger(ERPNextTestSuite): from frappe.utils import today frappe.db.set_single_value("Accounts Settings", "general_ledger_remarks_length", 50) - self.addCleanup(frappe.db.set_single_value, "Accounts Settings", "general_ledger_remarks_length", 0) - si = create_sales_invoice(company=self.company) - self.addCleanup(self._cancel_and_delete, "Sales Invoice", si.name) + create_sales_invoice(company=self.company) columns, data = execute( frappe._dict( @@ -42,15 +40,6 @@ class TestGeneralLedger(ERPNextTestSuite): self.assertTrue(data) self.assertTrue(any("remarks" in row for row in data)) - @staticmethod - def _cancel_and_delete(doctype, name): - if not frappe.db.exists(doctype, name): - return - doc = frappe.get_doc(doctype, name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc(doctype, name, force=1) - def clear_old_entries(self): doctype_list = [ "GL Entry", diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index cf70371e272..aff1216d747 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -642,7 +642,7 @@ class TestGrossProfit(ERPNextTestSuite): self.assertEqual(total.get("gross_profit_%"), -50.0) def test_sales_person_wise_gross_profit(self): - sales_person = make_sales_person("_Test Sales Person") + sales_person = frappe.get_doc("Sales Person", "_Test Sales Person") posting_date = get_first_day(nowdate()) qty = 10 @@ -1194,19 +1194,3 @@ class TestGrossProfit(ERPNextTestSuite): self.assertEqual(base_rate, 220.0) # avg selling rate = 220/1 self.assertEqual(gross_profit, 120.0) # 220 - 100 self.assertAlmostEqual(gp_percent, 54.545, places=2) # 120/220 * 100 - - -def make_sales_person(sales_person_name="_Test Sales Person"): - if not frappe.db.exists("Sales Person", {"sales_person_name": sales_person_name}): - sales_person_doc = frappe.get_doc( - { - "doctype": "Sales Person", - "is_group": 0, - "parent_sales_person": "Sales Team", - "sales_person_name": sales_person_name, - } - ).insert(ignore_permissions=True) - else: - sales_person_doc = frappe.get_doc("Sales Person", {"sales_person_name": sales_person_name}) - - return sales_person_doc diff --git a/erpnext/accounts/report/share_balance/test_share_balance.py b/erpnext/accounts/report/share_balance/test_share_balance.py index 0b91d1525f3..ae09cb59bac 100644 --- a/erpnext/accounts/report/share_balance/test_share_balance.py +++ b/erpnext/accounts/report/share_balance/test_share_balance.py @@ -12,7 +12,7 @@ COMPANY = "_Test Company" class TestShareBalanceReport(ERPNextTestSuite): def setUp(self): self.share_type = create_share_type("_Test Share Balance Equity") - self.shareholder = create_shareholder("_Test Share Balance Holder", COMPANY) + self.shareholder = get_shareholder("Iron Man", COMPANY) def test_date_filter_is_mandatory(self): self.assertRaises(frappe.ValidationError, execute, frappe._dict({"shareholder": self.shareholder})) @@ -96,7 +96,7 @@ class TestShareBalanceReport(ERPNextTestSuite): self.assertEqual(row[4], 3000) def test_balance_reduces_after_transfer_out(self): - other_holder = create_shareholder("_Test Share Balance Holder 2", COMPANY) + other_holder = get_shareholder("Thor", COMPANY) create_share_transfer( transfer_type="Issue", to_shareholder=self.shareholder, @@ -187,9 +187,8 @@ def create_share_type(title): return title -def create_shareholder(title, company): - shareholder = frappe.get_doc({"doctype": "Shareholder", "title": title, "company": company}).insert() - return shareholder.name +def get_shareholder(title, company): + return frappe.db.get_value("Shareholder", {"title": title, "company": company}, "name") def create_share_transfer(**kwargs): diff --git a/erpnext/accounts/report/share_ledger/test_share_ledger.py b/erpnext/accounts/report/share_ledger/test_share_ledger.py index 51309bd9f94..72769a805e1 100644 --- a/erpnext/accounts/report/share_ledger/test_share_ledger.py +++ b/erpnext/accounts/report/share_ledger/test_share_ledger.py @@ -23,7 +23,7 @@ COL_SHARE_TRANSFER = 8 class TestShareLedger(ERPNextTestSuite): def setUp(self): - self.shareholder = self.create_shareholder("_Test Share Ledger Holder") + self.shareholder = self.get_shareholder("Iron Man") # Issue 100 shares on 2026-06-01, then another 50 on 2026-06-10. self.first = self.issue_shares(date="2026-06-01", from_no=1, to_no=100, rate=10) self.second = self.issue_shares(date="2026-06-10", from_no=101, to_no=150, rate=12) @@ -72,7 +72,7 @@ class TestShareLedger(ERPNextTestSuite): self.assertEqual(data[0][COL_NO_OF_SHARES], 100) def test_transfer_type_label_when_shareholder_is_seller(self): - buyer = self.create_shareholder("_Test Share Ledger Buyer") + buyer = self.get_shareholder("Thor") transfer = self.make_transfer( from_shareholder=self.shareholder, to_shareholder=buyer, @@ -87,7 +87,7 @@ class TestShareLedger(ERPNextTestSuite): self.assertEqual(row[COL_TRANSFER_TYPE], f"Transfer to {buyer}") def test_transfer_type_label_when_shareholder_is_buyer(self): - seller = self.create_shareholder("_Test Share Ledger Seller") + seller = self.get_shareholder("Hulk") # the seller must own shares before it can transfer them self.issue_shares(date="2026-06-12", from_no=201, to_no=300, rate=10, shareholder=seller) transfer = self.make_transfer( @@ -119,15 +119,8 @@ class TestShareLedger(ERPNextTestSuite): self.assertIsNotNone(row, f"Share Transfer {transfer_name} missing from ledger") return row - def create_shareholder(self, title): - doc = frappe.get_doc( - { - "doctype": "Shareholder", - "title": title, - "company": COMPANY, - } - ).insert() - return doc.name + def get_shareholder(self, title): + return frappe.db.get_value("Shareholder", {"title": title, "company": COMPANY}, "name") def issue_shares(self, date, from_no, to_no, rate, shareholder=None): doc = frappe.get_doc( diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index fa454c45c5f..c582ef6dc14 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -2109,13 +2109,17 @@ def create_asset_category(enable_cwip=1): def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0, asset_category=None): + item_code = item_code or "Macbook Pro" + if frappe.db.exists("Item", item_code): + return frappe.get_doc("Item", item_code) + meta = frappe.get_meta("Asset") naming_series = meta.get_field("naming_series").options.splitlines()[0] or "ACC-ASS-.YYYY.-" try: item = frappe.get_doc( { "doctype": "Item", - "item_code": item_code or "Macbook Pro", + "item_code": item_code, "item_name": "Macbook Pro", "description": "Macbook Pro Retina Display", "asset_category": asset_category or "Computers", diff --git a/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py b/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py index 5e1f08edad0..f57ce8313d1 100644 --- a/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py +++ b/erpnext/assets/doctype/asset_capitalization/services/gl_composer.py @@ -54,14 +54,16 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer): for item_row in doc.stock_items: sle_list = self.sle_map.get(item_row.name) if sle_list: - _inv_dict = doc.get_inventory_account_dict(item_row, self.inventory_account_map) for sle in sle_list: stock_value_difference = flt(sle.stock_value_difference, self.precision) if erpnext.is_perpetual_inventory_enabled(doc.company): + _inv_dict = doc.get_inventory_account_dict(item_row, self.inventory_account_map) account = _inv_dict["account"] + account_currency = _inv_dict["account_currency"] else: account = doc.get_company_default("default_expense_account") + account_currency = None target_against.add(account) gl_entries.append( @@ -74,7 +76,7 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer): "remarks": doc.get("remarks") or "Accounting Entry for Stock", "credit": -1 * stock_value_difference, }, - _inv_dict["account_currency"], + account_currency, item=item_row, ) ) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index fec8a7c0053..6517c36b131 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -440,7 +440,11 @@ def create_asset_capitalization(**args): target_asset = frappe.get_doc("Asset", args.target_asset) if args.target_asset else frappe._dict() target_item_code = target_asset.item_code or args.target_item_code company = target_asset.company or args.company or "_Test Company" - warehouse = args.warehouse or create_warehouse("_Test Warehouse", company=company) + warehouse = args.warehouse or ( + "_Test Warehouse - _TC" + if company == "_Test Company" + else create_warehouse("_Test Warehouse", company=company) + ) source_warehouse = args.source_warehouse or warehouse asset_capitalization = frappe.new_doc("Asset Capitalization") diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index b84bbfba8aa..d5d78cdc0df 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -20,7 +20,6 @@ from erpnext.assets.doctype.asset.test_asset import ( from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, ) -from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( get_serial_nos_from_bundle, make_serial_batch_bundle, @@ -32,7 +31,6 @@ class TestAssetRepair(ERPNextTestSuite): def setUp(self): self.load_test_records("Stock Entry") set_depreciation_settings_in_company() - create_item("_Test Stock Item") def test_asset_status(self): date = nowdate() diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 86681eed676..084744079dc 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -378,9 +378,7 @@ class TestPurchaseOrder(ERPNextTestSuite): po.submit() first_item_of_po = po.get("items")[0] - company_default = frappe.db.get_value("Company", po.company, "default_warehouse") frappe.db.set_value("Company", po.company, "default_warehouse", None) - self.addCleanup(frappe.db.set_value, "Company", po.company, "default_warehouse", company_default) def get_trans_items(item_code): return json.dumps( @@ -794,14 +792,9 @@ class TestPurchaseOrder(ERPNextTestSuite): self.assertRaises(frappe.ValidationError, below_minimum.insert) def test_marginal_min_order_qty_overage_toast(self): - original_precision = frappe.db.get_default("float_precision") frappe.db.set_default("float_precision", "3") - self.addCleanup(frappe.db.set_default, "float_precision", original_precision) - if not frappe.db.exists("UOM", "Gram"): - frappe.get_doc({"doctype": "UOM", "uom_name": "Gram"}).insert() - - item_doc = make_item(properties={"min_order_qty": 50000, "stock_uom": "Gram"}) + item_doc = make_item(properties={"min_order_qty": 50000, "stock_uom": "_Test UOM 1"}) item_doc.append("uoms", {"uom": "Pound", "conversion_factor": 453.592292197}) item_doc.save() item = item_doc.name @@ -1799,25 +1792,11 @@ def create_po_for_sc_testing(): def prepare_data_for_internal_transfer(): - from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier - from erpnext.selling.doctype.customer.test_customer import create_internal_customer from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse company = "_Test Company with perpetual inventory" - create_internal_customer( - "_Test Internal Customer 2", - company, - company, - ) - - create_internal_supplier( - "_Test Internal Supplier 2", - company, - company, - ) - warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) create_warehouse("_Test Internal Warehouse GIT", company=company) diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py index 267b7d04e9f..5cb07bff471 100644 --- a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py @@ -15,6 +15,7 @@ from erpnext.buying.doctype.request_for_quotation.test_request_for_quotation imp from erpnext.buying.doctype.supplier_quotation.mapper import make_purchase_order from erpnext.buying.doctype.supplier_quotation.supplier_quotation import set_expired_status from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate +from erpnext.tests.assertions import assert_raises_with_savepoint from erpnext.tests.utils import ERPNextTestSuite @@ -161,12 +162,9 @@ class TestPurchaseOrder(ERPNextTestSuite): ] ) - frappe.db.savepoint("before_cancel") # check if item having purchase order can be removed - self.assertRaises( - frappe.LinkExistsError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name - ) - frappe.db.rollback(save_point="before_cancel") + with assert_raises_with_savepoint(self, frappe.LinkExistsError): + update_child_qty_rate("Supplier Quotation", trans_item, sq.name) trans_item = json.dumps( [ diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index da18c84f516..61b2e6cfabe 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -53,7 +53,6 @@ class TestAccountsController(ERPNextTestSuite): self.item = "_Test Item" self.customer = "_Test Customer USD" self.supplier = "_Test Supplier USD" - self.create_account() frappe.flags.is_reverse_depr_entry = False def create_account(self): @@ -99,6 +98,7 @@ class TestAccountsController(ERPNextTestSuite): setattr(self, x.attribute_name, acc.name) def setup_advance_accounts_in_party_master(self): + self.create_account() company = frappe.get_doc("Company", self.company) company.book_advance_payments_in_separate_party_account = 1 company.save() diff --git a/erpnext/controllers/tests/test_sales_and_purchase_return.py b/erpnext/controllers/tests/test_sales_and_purchase_return.py index 55124e319ab..8981a29c623 100644 --- a/erpnext/controllers/tests/test_sales_and_purchase_return.py +++ b/erpnext/controllers/tests/test_sales_and_purchase_return.py @@ -7,15 +7,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestSalesAndPurchaseReturn(ERPNextTestSuite): - @staticmethod - def _cancel_and_delete(doctype, name): - if not frappe.db.exists(doctype, name): - return - doc = frappe.get_doc(doctype, name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc(doctype, name, force=1) - def test_sales_return_validates_against_original(self): # Submitting a return Delivery Note runs validate_returned_items (Item / Packed Item lookups # via frappe.get_all) and get_already_returned_items (qb GROUP BY of the returned qty) -- both @@ -24,16 +15,13 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite): from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100) - self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name) + make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100) dn = create_delivery_note(qty=5) - self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name) return_dn = make_sales_return(dn.name) return_dn.insert() return_dn.submit() - self.addCleanup(self._cancel_and_delete, "Delivery Note", return_dn.name) self.assertEqual(return_dn.is_return, 1) self.assertEqual(return_dn.items[0].qty, -5) @@ -44,7 +32,6 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite): from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice pi = make_purchase_invoice(qty=10) - self.addCleanup(self._cancel_and_delete, "Purchase Invoice", pi.name) return_pi = make_purchase_invoice( is_return=1, @@ -66,7 +53,6 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite): pi.items[0].item_code = "" pi.save() pi.submit() - self.addCleanup(self._cancel_and_delete, "Purchase Invoice", pi.name) return_pi = make_purchase_invoice( item_name="_Test Item", @@ -86,11 +72,9 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite): from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100) - self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name) + make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100) dn = create_delivery_note(qty=5) - self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name) return_dn = make_sales_return(dn.name) return_dn.items[0].qty = 0 @@ -104,7 +88,6 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite): from erpnext.controllers.sales_and_purchase_return import make_return_doc si = create_sales_invoice(qty=10) - self.addCleanup(self._cancel_and_delete, "Sales Invoice", si.name) return_si = make_return_doc(si.doctype, si.name) return_si.items[0].qty = 0 @@ -131,14 +114,11 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite): si.items[0].stock_uom = "Kg" si.items[0].conversion_factor = 0.013888889 si.save().submit() - self.addCleanup(self._cancel_and_delete, "Sales Invoice", si.name) first_return = make_return_doc(si.doctype, si.name) first_return.items[0].qty = -24 first_return.save().submit() - self.addCleanup(self._cancel_and_delete, "Sales Invoice", first_return.name) second_return = make_return_doc(si.doctype, si.name) self.assertEqual(second_return.items[0].qty, -24) second_return.save().submit() - self.addCleanup(self._cancel_and_delete, "Sales Invoice", second_return.name) diff --git a/erpnext/controllers/tests/test_selling_controller.py b/erpnext/controllers/tests/test_selling_controller.py index 16002caaed2..98794f8aaa9 100644 --- a/erpnext/controllers/tests/test_selling_controller.py +++ b/erpnext/controllers/tests/test_selling_controller.py @@ -7,15 +7,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestSellingControllerConversions(ERPNextTestSuite): - @staticmethod - def _cancel_and_delete(doctype, name): - if not frappe.db.exists(doctype, name): - return - doc = frappe.get_doc(doctype, name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc(doctype, name, force=1) - def test_partial_delivery_updates_sales_order_status(self): # Submitting a Delivery Note against a Sales Order calls # SellingController.get_already_delivered_qty / get_so_qty_and_warehouse and StatusUpdater @@ -24,8 +15,7 @@ class TestSellingControllerConversions(ERPNextTestSuite): from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100) - self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name) + make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100) so = make_sales_order(qty=10) @@ -33,7 +23,6 @@ class TestSellingControllerConversions(ERPNextTestSuite): dn.items[0].qty = 4 dn.insert() dn.submit() - self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name) so.reload() self.assertEqual(so.per_delivered, 40.0) diff --git a/erpnext/controllers/tests/test_stock_controller.py b/erpnext/controllers/tests/test_stock_controller.py index 0c80050921e..90f7c2a62b8 100644 --- a/erpnext/controllers/tests/test_stock_controller.py +++ b/erpnext/controllers/tests/test_stock_controller.py @@ -8,15 +8,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestStockControllerConversions(ERPNextTestSuite): - @staticmethod - def _cancel_and_delete(doctype, name): - if not frappe.db.exists(doctype, name): - return - doc = frappe.get_doc(doctype, name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc(doctype, name, force=1) - def test_future_sle_exists_detects_later_entries(self): # future_sle_exists / get_conditions_to_validate_future_sle were converted to query builder # (Count + Criterion.any). A later SLE for the same item+warehouse must be detected, which @@ -26,8 +17,7 @@ class TestStockControllerConversions(ERPNextTestSuite): from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry item = make_item("_Test Future SLE Item", {"is_stock_item": 1}).name - se = make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=10, basic_rate=100) - self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name) + make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=10, basic_rate=100) # Pretend a different voucher posts a day earlier for the same item/warehouse: the existing # (later) SLE must be reported as a future entry. @@ -52,7 +42,6 @@ class TestStockControllerConversions(ERPNextTestSuite): posting_date=add_days(today(), -5), posting_time="01:00:00", ) - self.addCleanup(self._cancel_and_delete, "Stock Entry", opening.name) return opening @@ -106,7 +95,6 @@ class TestStockControllerConversions(ERPNextTestSuite): finally: stock_ledger.make_entry = original_make_entry - self.addCleanup(self._cancel_and_delete, "Stock Entry", entry.name) if inject is not None: self.assertTrue(injected, "the later SL Entry was not written during the submit") @@ -126,9 +114,6 @@ class TestStockControllerConversions(ERPNextTestSuite): pluck="name", ) ) - for name in names: - self.addCleanup(frappe.delete_doc, "Repost Item Valuation", name, force=1) - return names def test_repost_queued_for_entry_backdated_while_its_sl_entries_were_written(self): diff --git a/erpnext/edi/doctype/code_list/test_code_list_import.py b/erpnext/edi/doctype/code_list/test_code_list_import.py index 949544bd633..d44983689ac 100644 --- a/erpnext/edi/doctype/code_list/test_code_list_import.py +++ b/erpnext/edi/doctype/code_list/test_code_list_import.py @@ -1,6 +1,7 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt +from contextlib import contextmanager from unittest.mock import Mock, patch import frappe @@ -51,12 +52,9 @@ SAMPLE_GENERICODE = b""" class TestCodeListImport(ERPNextTestSuite): def test_import_genericode_rejects_remote_file_url(self): - self.set_upload_context( - file_name="trusted.xml", - file_url="https://example.com/codelists/trusted.xml", - ) - - with patch("erpnext.edi.doctype.code_list.code_list_import.requests.get") as mock_get: + with self.upload_context( + file_name="trusted.xml", file_url="https://example.com/codelists/trusted.xml" + ), patch("erpnext.edi.doctype.code_list.code_list_import.requests.get") as mock_get: with self.assertRaisesRegex( frappe.ValidationError, "Importing Code Lists from remote URLs is not allowed." ): @@ -65,12 +63,9 @@ class TestCodeListImport(ERPNextTestSuite): mock_get.assert_not_called() def test_import_genericode_rejects_file_scheme_url(self): - self.set_upload_context( - file_name="trusted.xml", - file_url="file:///tmp/trusted.xml", - ) - - with patch("erpnext.edi.doctype.code_list.code_list_import.requests.get") as mock_get: + with self.upload_context(file_name="trusted.xml", file_url="file:///tmp/trusted.xml"), patch( + "erpnext.edi.doctype.code_list.code_list_import.requests.get" + ) as mock_get: with self.assertRaisesRegex( frappe.ValidationError, "Importing Code Lists from remote URLs is not allowed." ): @@ -110,36 +105,34 @@ class TestCodeListImport(ERPNextTestSuite): code_list_import.import_genericode_from_url("https://example.com/codelists/trusted.xml") def test_import_genericode_from_uploaded_file_returns_metadata(self): - self.set_upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml") + with self.upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml"): + import_result = code_list_import.import_genericode() - import_result = code_list_import.import_genericode() + self.assert_import_response(import_result) - self.assert_import_response(import_result) - - file_doc = frappe.get_doc("File", import_result["file"]) - self.assertEqual(file_doc.get_content(encodings=()), SAMPLE_GENERICODE) + file_doc = frappe.get_doc("File", import_result["file"]) + self.assertEqual(file_doc.get_content(encodings=()), SAMPLE_GENERICODE) def test_process_genericode_import_reads_file_doc_content(self): - self.set_upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml") + with self.upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml"): + import_result = code_list_import.import_genericode() + count = code_list_import.process_genericode_import( + code_list_name=import_result["code_list"], + file_name=import_result["file"], + code_column="code", + title_column="name", + ) - import_result = code_list_import.import_genericode() - count = code_list_import.process_genericode_import( - code_list_name=import_result["code_list"], - file_name=import_result["file"], - code_column="code", - title_column="name", - ) - - self.assertEqual(count, 3) - self.assertEqual(frappe.db.count("Common Code", {"code_list": import_result["code_list"]}), 3) - self.assertEqual( - frappe.db.get_value( - "Common Code", - {"code_list": import_result["code_list"], "common_code": "A"}, - "title", - ), - "Alpha", - ) + self.assertEqual(count, 3) + self.assertEqual(frappe.db.count("Common Code", {"code_list": import_result["code_list"]}), 3) + self.assertEqual( + frappe.db.get_value( + "Common Code", + {"code_list": import_result["code_list"], "common_code": "A"}, + "title", + ), + "Alpha", + ) def test_import_genericode_from_local_file_url(self): source_file = frappe.get_doc( @@ -150,32 +143,38 @@ class TestCodeListImport(ERPNextTestSuite): "is_private": 1, } ).insert() - self.set_upload_context(file_name=source_file.file_name, file_url=source_file.file_url) + with self.upload_context(file_name=source_file.file_name, file_url=source_file.file_url): + import_result = code_list_import.import_genericode() - import_result = code_list_import.import_genericode() + self.assert_import_response(import_result) - self.assert_import_response(import_result) - - def set_upload_context( - self, + @staticmethod + @contextmanager + def upload_context( content: bytes | None = None, file_name: str = "genericode.xml", file_url: str | None = None, docname: str | None = None, ): - attrs = ("form_dict", "uploaded_file", "uploaded_file_url", "uploaded_filename") - originals = {attr: getattr(frappe.local, attr, None) for attr in attrs} + missing = object() + attrs = { + "form_dict": frappe._dict(doctype="Code List", docname=docname), + "uploaded_file": content, + "uploaded_file_url": file_url, + "uploaded_filename": file_name, + } + originals = {key: getattr(frappe.local, key, missing) for key in attrs} + for key, value in attrs.items(): + setattr(frappe.local, key, value) - frappe.local.form_dict = frappe._dict(doctype="Code List", docname=docname) - frappe.local.uploaded_file = content - frappe.local.uploaded_file_url = file_url - frappe.local.uploaded_filename = file_name - - def restore(): - for attr, value in originals.items(): - setattr(frappe.local, attr, value) - - self.addCleanup(restore) + try: + yield + finally: + for key, value in originals.items(): + if value is missing: + delattr(frappe.local, key) + else: + setattr(frappe.local, key, value) def assert_import_response(self, import_result): self.assertEqual( diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 51ccc25d50d..88dc828bb55 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -70,6 +70,8 @@ after_install = "erpnext.setup.install.after_install" after_app_install = "erpnext.setup.install.after_app_install" after_app_uninstall = "erpnext.setup.install.after_app_uninstall" +before_tests = "erpnext.tests.utils.bootstrap_test_data" + boot_session = "erpnext.startup.boot.boot_session" notification_config = "erpnext.startup.notifications.get_notification_config" get_help_messages = "erpnext.utilities.activation.get_help_messages" diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 208674f0963..b2084b40e65 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -18,25 +18,6 @@ class TestMaintenanceSchedule(ERPNextTestSuite): def setUp(self): self.load_test_records("Stock Entry") - @classmethod - def make_sales_person(cls): - records = [ - { - "doctype": "Sales Person", - "is_group": 0, - "parent_sales_person": "Sales Team", - "sales_person_name": "_Test Sales Person", - }, - ] - cls.sales_person = [] - for x in records: - if not frappe.db.exists("Sales Person", {"sales_person_name": x.get("sales_person_name")}): - cls.sales_person.append(frappe.get_doc(x).insert()) - else: - cls.sales_person.append( - frappe.get_doc("Sales Person", {"sales_person_name": x.get("sales_person_name")}) - ) - def test_events_should_be_created_and_deleted(self): ms = make_maintenance_schedule() ms.generate_schedule() diff --git a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py index 2bc01a8b089..215f39b0616 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py @@ -9,7 +9,7 @@ from erpnext.tests.utils import ERPNextTestSuite class TestMaintenanceVisit(ERPNextTestSuite): def setUp(self): - self.sales_person = make_sales_person("_Test Maintenance Service Person") + self.sales_person = frappe.get_doc("Sales Person", "_Test Sales Person") def make_warranty_claim(self): # Warranty Claim is not submittable; it provides a real target for the @@ -129,14 +129,6 @@ class TestMaintenanceVisit(ERPNextTestSuite): self.assertIsNone(claim.resolution_date) -def make_sales_person(name): - sales_person = frappe.get_doc({"doctype": "Sales Person", "sales_person_name": name}) - sales_person.insert(ignore_if_duplicate=True) - if not sales_person.name: - sales_person = frappe.get_doc("Sales Person", {"sales_person_name": name}) - return sales_person - - def make_maintenance_visit(): mv = frappe.new_doc("Maintenance Visit") mv.company = "_Test Company" @@ -144,8 +136,6 @@ def make_maintenance_visit(): mv.mntc_date = today() mv.completion_status = "Partially Completed" - sales_person = make_sales_person("Dwight Schrute") - mv.append( "purposes", { @@ -153,7 +143,7 @@ def make_maintenance_visit(): "sales_person": "Sales Team", "description": "Test Item", "work_done": "Test Work Done", - "service_person": sales_person.name, + "service_person": "_Test Sales Person", }, ) mv.insert(ignore_permissions=True) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2732aa7046e..d6dc5d4d195 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2389,9 +2389,7 @@ class TestProductionPlan(ERPNextTestSuite): _quantity_in_purchase_uom, ) - original_precision = frappe.db.get_default("float_precision") frappe.db.set_default("float_precision", "3") - self.addCleanup(frappe.db.set_default, "float_precision", original_precision) self.assertEqual(_quantity_in_purchase_uom(50000, 453.592292197, 50000), 110.232) self.assertEqual(_quantity_in_purchase_uom(2000, 0.453592, 2000), 4409.249) @@ -2399,9 +2397,7 @@ class TestProductionPlan(ERPNextTestSuite): self.assertEqual(_quantity_in_purchase_uom(50000, 453.592292197), 110.231) def test_min_order_qty_grid_ceiling_in_plan_items(self): - original_precision = frappe.db.get_default("float_precision") frappe.db.set_default("float_precision", "3") - self.addCleanup(frappe.db.set_default, "float_precision", original_precision) conversion_factor = 453.592292197 fg_item = make_item(properties={"is_stock_item": 1}).name @@ -2422,9 +2418,7 @@ class TestProductionPlan(ERPNextTestSuite): def test_min_order_qty_grid_ceiling_from_other_locations(self): from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - original_precision = frappe.db.get_default("float_precision") frappe.db.set_default("float_precision", "3") - self.addCleanup(frappe.db.set_default, "float_precision", original_precision) conversion_factor = 453.592292197 fg_item = make_item(properties={"is_stock_item": 1}).name diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 1cec3b657b4..75311176b37 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -85,6 +85,33 @@ class TestRouting(ERPNextTestSuite): self.assertEqual(bom_doc.operations[0].time_in_mins, 30) self.assertEqual(bom_doc.operations[1].time_in_mins, 20) + def test_create_routing_isolates_operation_lists(self): + first = create_routing( + routing_name="Testing Route Isolation", + operations=[ + { + "operation": "_Test Operation 1", + "workstation": "_Test Workstation 1", + "time_in_mins": 10, + } + ], + ) + second = create_routing( + routing_name="Testing Route Isolation", + operations=[ + { + "operation": "_Test Operation 1", + "workstation": "_Test Workstation 1", + "time_in_mins": 20, + } + ], + ) + + first.reload() + self.assertNotEqual(first.name, second.name) + self.assertEqual(first.operations[0].time_in_mins, 10) + self.assertEqual(second.operations[0].time_in_mins, 20) + def setup_operations(rows): from erpnext.manufacturing.doctype.operation.test_operation import make_operation @@ -102,17 +129,8 @@ def create_routing(**args): doc.update(args) if not args.do_not_save: - frappe.db.savepoint("create_routing") - try: - doc.insert() - except frappe.DuplicateEntryError: - frappe.db.rollback(save_point="create_routing") - doc = frappe.get_doc("Routing", args.routing_name) - doc.delete_key("operations") - for operation in args.operations: - doc.append("operations", operation) - - doc.save() + doc.routing_name = f"{args.routing_name}-{frappe.generate_hash(length=10)}" + doc.insert() return doc @@ -136,7 +154,11 @@ def setup_bom(**args): args.raw_materials = ["Test Extra Item N-1"] - name = frappe.db.get_value("BOM", {"item": args.item_code}, "name") + name = frappe.db.get_value( + "BOM", + {"item": args.item_code, "routing": args.routing, "docstatus": 1}, + "name", + ) if not name: bom_doc = make_bom( item=args.item_code, diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 571ae370999..1121c9fedbf 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -44,7 +44,6 @@ class TestWorkOrder(ERPNextTestSuite): def setUp(self): self.warehouse = "_Test Warehouse 2 - _TC" self.item = "_Test Item" - prepare_data_for_backflush_based_on_materials_transferred() def check_planned_qty(self): planned0 = ( @@ -1358,7 +1357,7 @@ class TestWorkOrder(ERPNextTestSuite): wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True) serial_nos = self.get_serial_nos_for_fg(wo_order.name) - stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) stock_entry.set_work_order_details() for row in stock_entry.items: if row.item_code == fg_item: @@ -1395,10 +1394,10 @@ class TestWorkOrder(ERPNextTestSuite): item.save() try: - wo_order = make_wo_order_test_record(item=fg_item, batch_size=5, qty=10, skip_transfer=True) + wo_order = make_wo_order_test_record(item=fg_item, batch_size=1, qty=2, skip_transfer=True) serial_nos = self.get_serial_nos_for_fg(wo_order.name) - stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) stock_entry.set_work_order_details() for row in stock_entry.items: if row.item_code == fg_item: @@ -2191,6 +2190,7 @@ class TestWorkOrder(ERPNextTestSuite): self.assertRaises(frappe.ValidationError, pick_list.submit) def test_backflushed_batch_raw_materials_based_on_transferred(self): + prepare_data_for_backflush_based_on_materials_transferred() frappe.db.set_single_value( "Manufacturing Settings", "backflush_raw_materials_based_on", @@ -2263,6 +2263,7 @@ class TestWorkOrder(ERPNextTestSuite): self.assertEqual(abs(d.qty), 2) def test_backflushed_serial_no_raw_materials_based_on_transferred(self): + prepare_data_for_backflush_based_on_materials_transferred() frappe.db.set_single_value( "Manufacturing Settings", "backflush_raw_materials_based_on", @@ -2310,6 +2311,7 @@ class TestWorkOrder(ERPNextTestSuite): self.assertEqual(manufacture_ste_doc2.items[0].qty, 3) def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self): + prepare_data_for_backflush_based_on_materials_transferred() frappe.db.set_single_value( "Manufacturing Settings", "backflush_raw_materials_based_on", @@ -2395,6 +2397,7 @@ class TestWorkOrder(ERPNextTestSuite): self.assertFalse(serial_nos) def test_backflushed_batch_raw_materials_based_on_transferred_autosabb(self): + prepare_data_for_backflush_based_on_materials_transferred() frappe.db.set_single_value( "Manufacturing Settings", "backflush_raw_materials_based_on", @@ -2461,6 +2464,7 @@ class TestWorkOrder(ERPNextTestSuite): self.assertEqual(manufacture_ste_doc.items[0].qty, 4.0) def test_backflushed_serial_no_raw_materials_based_on_transferred_autosabb(self): + prepare_data_for_backflush_based_on_materials_transferred() frappe.db.set_single_value( "Manufacturing Settings", "backflush_raw_materials_based_on", @@ -2528,6 +2532,7 @@ class TestWorkOrder(ERPNextTestSuite): self.assertEqual(manufacture_ste_doc.items[0].qty, 4.0) def test_backflushed_serial_no_batch_raw_materials_based_on_transferred_autosabb(self): + prepare_data_for_backflush_based_on_materials_transferred() frappe.db.set_single_value( "Manufacturing Settings", "backflush_raw_materials_based_on", @@ -2586,6 +2591,7 @@ class TestWorkOrder(ERPNextTestSuite): ### def test_non_consumed_material_return_against_work_order(self): + prepare_data_for_backflush_based_on_materials_transferred() frappe.db.set_single_value( "Manufacturing Settings", "backflush_raw_materials_based_on", diff --git a/erpnext/manufacturing/report/production_planning_report/test_production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/test_production_planning_report.py index 10a427aa38e..c3b23c7eb48 100644 --- a/erpnext/manufacturing/report/production_planning_report/test_production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/test_production_planning_report.py @@ -14,12 +14,10 @@ class TestProductionPlanningReport(ERPNextTestSuite): wh = "_Test Warehouse - _TC" wo = make_wo_order_test_record(production_item="_Test FG Item", qty=2, source_warehouse=wh) - self.addCleanup(self._cancel_and_delete, "Work Order", wo.name) rm = wo.required_items[0].item_code for qty in (3, 4): - po = create_purchase_order(item_code=rm, warehouse=wh, qty=qty, rate=10) - self.addCleanup(self._cancel_and_delete, "Purchase Order", po.name) + create_purchase_order(item_code=rm, warehouse=wh, qty=qty, rate=10) filters = { "company": "_Test Company", @@ -34,14 +32,3 @@ class TestProductionPlanningReport(ERPNextTestSuite): self.assertTrue(rm_rows) # both on-order PO lines (3 + 4) are summed, not arbitrary-picked self.assertEqual(rm_rows[0]["arrival_qty"], 7) - - @staticmethod - def _cancel_and_delete(doctype, name): - import frappe - - if not frappe.db.exists(doctype, name): - return - doc = frappe.get_doc(doctype, name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc(doctype, name, force=1) diff --git a/erpnext/manufacturing/report/quality_inspection_summary/test_quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/test_quality_inspection_summary.py index 08401329126..ccc13bd168c 100644 --- a/erpnext/manufacturing/report/quality_inspection_summary/test_quality_inspection_summary.py +++ b/erpnext/manufacturing/report/quality_inspection_summary/test_quality_inspection_summary.py @@ -5,7 +5,6 @@ import frappe from frappe.utils import add_days, nowdate from erpnext.manufacturing.report.quality_inspection_summary.quality_inspection_summary import execute -from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.quality_inspection.test_quality_inspection import ( create_quality_inspection, make_minimal_job_card, @@ -16,7 +15,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestQualityInspectionSummary(ERPNextTestSuite): def setUp(self): super().setUp() - create_item("_Test Item") self.job_card = make_minimal_job_card(production_item="_Test Item") self.qi = create_quality_inspection( item_code="_Test Item", diff --git a/erpnext/manufacturing/scheduling/test_plan_adapter.py b/erpnext/manufacturing/scheduling/test_plan_adapter.py index 3e790fd3f38..348aa9f32f9 100644 --- a/erpnext/manufacturing/scheduling/test_plan_adapter.py +++ b/erpnext/manufacturing/scheduling/test_plan_adapter.py @@ -568,7 +568,6 @@ class TestPlanAdapter(ERPNextTestSuite): frappe.get_doc( {"doctype": "Item Lead Time", "item_code": "Test PPS RM", "purchase_time": 2} ).insert() - self.addCleanup(frappe.delete_doc, "Item Lead Time", "Test PPS RM", force=True) plan = self.make_plan() start_date = get_datetime("2026-11-02 09:00:00") @@ -636,7 +635,6 @@ class TestPlanAdapter(ERPNextTestSuite): ], } ).insert() - self.addCleanup(frappe.delete_doc, "Item Lead Time", item_code, force=True) @change_settings("Manufacturing Settings", {"mins_between_operations": 10, "allow_overtime": 0}) def test_schedule_uses_supplier_wise_lead_time(self): diff --git a/erpnext/projects/doctype/activity_cost/test_activity_cost.py b/erpnext/projects/doctype/activity_cost/test_activity_cost.py index 86083cf9813..757d14ae158 100644 --- a/erpnext/projects/doctype/activity_cost/test_activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/test_activity_cost.py @@ -29,7 +29,7 @@ class TestActivityCost(ERPNextTestSuite): self.assertRaises(DuplicationError, activity_cost2.insert) def test_default_activity_cost_title_and_duplication(self): - activity_type = self._activity_type("_Test Default Cost Type") + activity_type = "_Test Activity Type" default_cost = frappe.get_doc( { @@ -46,7 +46,7 @@ class TestActivityCost(ERPNextTestSuite): self.assertRaises(DuplicationError, duplicate.insert) def test_employee_name_and_title_are_set(self): - activity_type = self._activity_type("_Test Employee Cost Type") + activity_type = "_Test Activity Type" employee = frappe.db.get_all("Employee", filters={"first_name": "_Test Employee"})[0].name employee_name = frappe.db.get_value("Employee", employee, "employee_name") @@ -62,8 +62,3 @@ class TestActivityCost(ERPNextTestSuite): ).insert() self.assertEqual(cost.employee_name, employee_name) self.assertEqual(cost.title, f"{employee_name} for {activity_type}") - - def _activity_type(self, name): - if not frappe.db.exists("Activity Type", name): - frappe.get_doc({"doctype": "Activity Type", "activity_type": name}).insert() - return name diff --git a/erpnext/projects/doctype/project_update/test_project_update.py b/erpnext/projects/doctype/project_update/test_project_update.py index 2f83e26fdfe..0dd3ed449dd 100644 --- a/erpnext/projects/doctype/project_update/test_project_update.py +++ b/erpnext/projects/doctype/project_update/test_project_update.py @@ -42,7 +42,6 @@ class TestProjectUpdate(ERPNextTestSuite): "time": "10:00:00", } ).insert() - self.addCleanup(frappe.delete_doc, "Project Update", pu.name, force=1) # The converted update query (no longer referencing progress/progress_details) must find # yesterday's Project Update, keyed on project.name, on both engines. diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index a21baa74893..ddc70c7df68 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -400,7 +400,7 @@ class TestTimesheet(ERPNextTestSuite): customer = "_Test Customer" # tie the current user (Administrator) to the customer so the portal resolves it - contact = frappe.get_doc( + frappe.get_doc( { "doctype": "Contact", "first_name": "_Test Timesheet Portal Contact", @@ -408,7 +408,6 @@ class TestTimesheet(ERPNextTestSuite): "links": [{"link_doctype": "Customer", "link_name": customer}], } ).insert(ignore_permissions=True) - self.addCleanup(self._delete_if_exists, "Contact", contact.name) si = create_sales_invoice(customer=customer) @@ -464,11 +463,6 @@ class TestTimesheet(ERPNextTestSuite): timesheet.save() self.assertEqual(timesheet.get_title(), frappe.db.get_value("Employee", second, "employee_name")) - @staticmethod - def _delete_if_exists(doctype, name): - if frappe.db.exists(doctype, name): - frappe.delete_doc(doctype, name, force=True) - def make_timesheet( employee, diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 414b0c4df57..86646c38329 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -29,29 +29,30 @@ class TestCustomer(ERPNextTestSuite): company_currency = frappe.get_cached_value("Company", company, "default_currency") foreign_currency = "USD" if company_currency != "USD" else "EUR" + original_company = frappe.defaults.get_user_default("company") frappe.defaults.set_user_default("company", company) - self.addCleanup(frappe.defaults.clear_user_default, "company") + try: + # Master data seeds a current-dated exchange rate, so make_quotation should + # resolve that rate instead of falling back to the default conversion rate of 1.0. + expected_rate = get_exchange_rate(foreign_currency, company_currency, nowdate()) - # Master data seeds a current-dated exchange rate, so make_quotation should - # resolve that rate instead of falling back to the default conversion rate of 1.0. - expected_rate = get_exchange_rate(foreign_currency, company_currency, nowdate()) + customer = frappe.get_doc( + { + "doctype": "Customer", + "customer_name": "_Test Customer FX Quotation", + "customer_type": "Company", + "default_currency": foreign_currency, + } + ).insert() - customer = frappe.get_doc( - { - "doctype": "Customer", - "customer_name": "_Test Customer FX Quotation", - "customer_type": "Company", - "default_currency": foreign_currency, - } - ).insert() - self.addCleanup(frappe.delete_doc, "Customer", customer.name, force=1) + quotation = make_quotation(customer.name) - quotation = make_quotation(customer.name) - - self.assertEqual(quotation.currency, foreign_currency) - self.assertNotEqual(flt(quotation.conversion_rate), 1.0) - self.assertNotEqual(flt(quotation.conversion_rate), 0.0) - self.assertEqual(flt(quotation.conversion_rate), flt(expected_rate)) + self.assertEqual(quotation.currency, foreign_currency) + self.assertNotEqual(flt(quotation.conversion_rate), 1.0) + self.assertNotEqual(flt(quotation.conversion_rate), 0.0) + self.assertEqual(flt(quotation.conversion_rate), flt(expected_rate)) + finally: + frappe.defaults.set_user_default("company", original_company) def test_get_customer_name_dedupes_with_numeric_suffix(self): # When a customer name already exists, get_customer_name appends "- ". The @@ -63,7 +64,6 @@ class TestCustomer(ERPNextTestSuite): frappe.get_doc( {"doctype": "Customer", "customer_name": nm, "customer_type": "Individual"} ).insert() - self.addCleanup(frappe.delete_doc, "Customer", nm, force=1) doc = frappe.get_doc({"doctype": "Customer", "customer_name": base, "customer_type": "Individual"}) self.assertEqual(doc.get_customer_name(), f"{base} - 4") @@ -79,7 +79,6 @@ class TestCustomer(ERPNextTestSuite): frappe.get_doc( {"doctype": "Customer", "customer_name": nm, "customer_type": "Individual"} ).insert() - self.addCleanup(frappe.delete_doc, "Customer", nm, force=1) doc = frappe.get_doc({"doctype": "Customer", "customer_name": base, "customer_type": "Individual"}) self.assertEqual(doc.get_customer_name(), f"{base} - 4") @@ -510,9 +509,6 @@ class TestCustomer(ERPNextTestSuite): def test_overdue_billing_threshold_falls_back_to_customer_group(self): customer_group = frappe.get_cached_value("Customer", "_Test Customer", "customer_group") group = frappe.get_doc("Customer Group", customer_group) - customer = frappe.get_doc("Customer", "_Test Customer") - self._restore_credit_limits_after(group) - self._restore_credit_limits_after(customer) group.credit_limits = [] group.append("credit_limits", {"company": "_Test Company", "overdue_billing_threshold": 5000}) @@ -529,18 +525,6 @@ class TestCustomer(ERPNextTestSuite): set_overdue_billing_threshold("_Test Customer", "_Test Company", 0) self.assertEqual(get_overdue_billing_threshold("_Test Customer", "_Test Company"), 5000) - def _restore_credit_limits_after(self, doc): - original = [row.as_dict(no_default_fields=True) for row in doc.credit_limits] - - def restore(): - fresh = frappe.get_doc(doc.doctype, doc.name) - fresh.credit_limits = [] - for row in original: - fresh.append("credit_limits", row) - fresh.save() - - self.addCleanup(restore) - def test_overdue_threshold_row_without_credit_limit(self): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice diff --git a/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py b/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py index 2d9f7843e78..7052518d299 100644 --- a/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py +++ b/erpnext/selling/doctype/proforma_invoice/test_proforma_invoice.py @@ -2,9 +2,13 @@ # License: GNU General Public License v3. See license.txt import json +from contextlib import nullcontext +from io import BytesIO +from unittest.mock import patch import frappe from frappe.utils import flt +from pypdf import PdfWriter from erpnext.selling.doctype.proforma_invoice.proforma_invoice import ( get_sales_order_items, @@ -15,13 +19,34 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.tests.utils import ERPNextTestSuite +def _make_test_pdf(): + content = BytesIO() + writer = PdfWriter() + writer.add_blank_page(width=72, height=72) + writer.write(content) + return content.getvalue() + + +TEST_PDF = _make_test_pdf() + + class TestProformaInvoice(ERPNextTestSuite): def setUp(self): frappe.db.set_single_value("Selling Settings", "enable_proforma_invoice", 1) - def create_proforma(self, sales_order, lines, **kwargs): - items = [{"so_detail": so_detail, "qty": qty} for so_detail, qty in lines] - name = make_proforma_invoice(sales_order.name, json.dumps(items), **kwargs) + def create_proforma(self, sales_order, lines, use_real_pdf_renderer=False, **kwargs): + items = [line if isinstance(line, dict) else {"so_detail": line[0], "qty": line[1]} for line in lines] + pdf_renderer = ( + nullcontext() + if use_real_pdf_renderer + else patch.object( + frappe, + "attach_print", + return_value={"fname": "proforma.pdf", "fcontent": TEST_PDF}, + ) + ) + with pdf_renderer: + name = make_proforma_invoice(sales_order.name, json.dumps(items), **kwargs) return frappe.get_doc("Proforma Invoice", name) def test_partial_proforma_is_non_blocking(self): @@ -29,7 +54,7 @@ class TestProformaInvoice(ERPNextTestSuite): sales_order = make_sales_order(qty=10) so_detail = sales_order.items[0].name - proforma = self.create_proforma(sales_order, [(so_detail, 4)]) + proforma = self.create_proforma(sales_order, [(so_detail, 4)], use_real_pdf_renderer=True) self.assertEqual(proforma.status, "Issued") self.assertEqual(proforma.docstatus, 1) @@ -70,12 +95,11 @@ class TestProformaInvoice(ERPNextTestSuite): sales_order = make_sales_order(qty=10) # rate 100 so_detail = sales_order.items[0].name - name = make_proforma_invoice( - sales_order.name, - json.dumps([{"so_detail": so_detail, "qty": 5, "amount": 250}]), + proforma = self.create_proforma( + sales_order, + [{"so_detail": so_detail, "qty": 5, "amount": 250}], based_on="Amount", ) - proforma = frappe.get_doc("Proforma Invoice", name) self.assertEqual(proforma.based_on, "Amount") item = proforma.items[0] @@ -117,22 +141,22 @@ class TestProformaInvoice(ERPNextTestSuite): sales_order = make_sales_order(qty=10) so_detail = sales_order.items[0].name - amount_based = make_proforma_invoice( - sales_order.name, - json.dumps([{"so_detail": so_detail, "qty": 5, "amount": 250}]), + amount_based = self.create_proforma( + sales_order, + [{"so_detail": so_detail, "qty": 5, "amount": 250}], based_on="Amount", hide_item_qty=1, ) - self.assertEqual(frappe.db.get_value("Proforma Invoice", amount_based, "hide_item_qty"), 1) + self.assertEqual(amount_based.hide_item_qty, 1) # ignored outside Amount basis - qty_based = make_proforma_invoice( - sales_order.name, - json.dumps([{"so_detail": so_detail, "qty": 4}]), + qty_based = self.create_proforma( + sales_order, + [(so_detail, 4)], based_on="Quantity", hide_item_qty=1, ) - self.assertEqual(frappe.db.get_value("Proforma Invoice", qty_based, "hide_item_qty"), 0) + self.assertEqual(qty_based.hide_item_qty, 0) def test_feature_toggle_is_enforced(self): sales_order = make_sales_order(qty=10) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 22623366339..8b082355035 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -19,7 +19,7 @@ class TestQuotation(ERPNextTestSuite): def test_update_child_quotation_add_item(self): from erpnext.stock.doctype.item.test_item import make_item - item_1 = make_item("_Test Item") + item_1 = frappe.get_doc("Item", "_Test Item") item_2 = make_item("_Test Item 1") item_list = [ @@ -63,7 +63,7 @@ class TestQuotation(ERPNextTestSuite): def test_update_child_rate_change(self): from erpnext.stock.doctype.item.test_item import make_item - item_1 = make_item("_Test Item") + item_1 = frappe.get_doc("Item", "_Test Item") item_2 = make_item("_Test Item 1") item_list = [ @@ -924,13 +924,8 @@ class TestQuotation(ERPNextTestSuite): item = "_Test Item FOR UOM Validation" make_item(item, {"is_stock_item": 1}) - if not frappe.db.exists("UOM", "lbs"): - frappe.get_doc({"doctype": "UOM", "uom_name": "lbs", "must_be_whole_number": 1}).insert() - else: - frappe.db.set_value("UOM", "lbs", "must_be_whole_number", 1) - quotation = make_quotation(item_code=item, qty=1, rate=100, do_not_submit=1) - quotation.items[0].uom = "lbs" + quotation.items[0].uom = "_Test UOM" quotation.items[0].conversion_factor = 2.23 self.assertRaises(frappe.ValidationError, quotation.save) @@ -1007,7 +1002,6 @@ class TestQuotation(ERPNextTestSuite): from erpnext.selling.doctype.quotation.mapper import make_sales_order from erpnext.stock.doctype.item.test_item import make_item - make_item("_Test Item 2", {"is_stock_item": 1}) quotation = make_quotation(qty=0, do_not_save=1) quotation.append("items", {"item_code": "_Test Item 2", "qty": 10, "rate": 100}) quotation.submit() @@ -1041,8 +1035,6 @@ class TestQuotation(ERPNextTestSuite): from erpnext.stock.doctype.item.test_item import make_item # item code same but description different - make_item("_Test Item 2", {"is_stock_item": 1}) - quotation = make_quotation(qty=10, rate=100, do_not_submit=1) # duplicate items diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 691bd804472..9b0d8044a01 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -82,8 +82,10 @@ class TestSalesOrder(ERPNextTestSuite): self.assertEqual(frappe.db.get_value("Item Price", all_item_prices[0].name, "price_list_rate"), 1000) def test_sales_order_with_product_bundle_for_partial_material_request(self): - product_bundle = make_product_bundle( - "_Test Product Bundle Item", ["_Test Item", "_Test Item Home Desktop 100"] + from erpnext.selling.doctype.product_bundle.product_bundle import get_active_product_bundle + + product_bundle = frappe.get_doc( + "Product Bundle", get_active_product_bundle("_Test Product Bundle Item") ) so = make_sales_order(item_code=product_bundle.new_item_code, qty=2) mr = make_material_request(so.name) @@ -274,10 +276,10 @@ class TestSalesOrder(ERPNextTestSuite): so.append("items", {"item_code": "_Test Item 2", "qty": 1, "rate": -10}) so.save() - with self.assertRaises(frappe.ValidationError) as error: + with self.assertRaises(frappe.ValidationError): so.submit() - self.assertIn("selling-settings", str(error.exception)) + self.assertIn("selling-settings", frappe.local.message_log[-1]["message"]) @ERPNextTestSuite.change_settings("Selling Settings", {"allow_negative_rates_for_items": 1}) def test_sales_order_negative_rate_setting_does_not_allow_negative_quantity(self): @@ -872,9 +874,7 @@ class TestSalesOrder(ERPNextTestSuite): existing_item = so.get("items")[0] # a company gets a default warehouse when its warehouses are created - company_default = frappe.db.get_value("Company", so.company, "default_warehouse") frappe.db.set_value("Company", so.company, "default_warehouse", None) - self.addCleanup(frappe.db.set_value, "Company", so.company, "default_warehouse", company_default) def get_trans_items(warehouse=None): new_row = {"item_code": item_code, "rate": 200, "qty": 7} diff --git a/erpnext/selling/report/lost_quotations/test_lost_quotations.py b/erpnext/selling/report/lost_quotations/test_lost_quotations.py index 857fbb3b897..ffafde41941 100644 --- a/erpnext/selling/report/lost_quotations/test_lost_quotations.py +++ b/erpnext/selling/report/lost_quotations/test_lost_quotations.py @@ -17,13 +17,10 @@ class TestLostQuotations(ERPNextTestSuite): def test_lost_quotations_percentage_is_not_integer_divided(self): # `lost_quotations_pct` is count(group) / count(total) * 100. count/count is integer division on # Postgres, which truncates a proper fraction to 0; this asserts the percentage stays fractional. - quotations = [] # reason A on one quotation, reason B on three -> A is a strict minority of the total - quotations.append(self._make_lost_quotation(self.reason_a)) + self._make_lost_quotation(self.reason_a) for _ in range(3): - quotations.append(self._make_lost_quotation(self.reason_b)) - for qo in quotations: - self.addCleanup(self._cancel_and_delete, qo.name) + self._make_lost_quotation(self.reason_b) _columns, data = execute( frappe._dict({"company": self.company, "timespan": "This Year", "group_by": "Lost Reason"}) @@ -37,27 +34,11 @@ class TestLostQuotations(ERPNextTestSuite): self.assertLess(row_a[2], 100) def _ensure_lost_reason(self, name): - # only clean up reasons this test created, so a pre-existing master is left intact if not frappe.db.exists("Quotation Lost Reason", name): frappe.get_doc({"doctype": "Quotation Lost Reason", "order_lost_reason": name}).insert() - self.addCleanup(self._delete_lost_reason, name) return name - @staticmethod - def _delete_lost_reason(name): - if frappe.db.exists("Quotation Lost Reason", name): - frappe.delete_doc("Quotation Lost Reason", name, force=1) - def _make_lost_quotation(self, reason): qo = make_quotation(company=self.company, qty=1, rate=100) qo.declare_enquiry_lost([{"lost_reason": reason}], []) return qo - - @staticmethod - def _cancel_and_delete(name): - if not frappe.db.exists("Quotation", name): - return - doc = frappe.get_doc("Quotation", name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc("Quotation", name, force=1) diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index 29049a54794..4df6ff92faa 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -135,7 +135,7 @@ def make_transactions(company): for item in json.loads(data): create_transaction(item, company, start_date) - convert_order_to_invoices() + convert_order_to_invoices(company) frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0) @@ -164,12 +164,15 @@ def create_transaction(doctype, company, start_date): doc.submit() -def convert_order_to_invoices(): +def convert_order_to_invoices(company): for document in ["Purchase Order", "Sales Order"]: # Keep some orders intentionally unbilled/unpaid for i, order in enumerate( frappe.db.get_all( - document, filters={"docstatus": 1}, fields=["name", "transaction_date"], limit=6 + document, + filters={"docstatus": 1, "company": company}, + fields=["name", "transaction_date"], + limit=6, ) ): if document == "Purchase Order": diff --git a/erpnext/setup/doctype/authorization_control/test_authorization_control.py b/erpnext/setup/doctype/authorization_control/test_authorization_control.py index 0e1d36165db..314323f776d 100644 --- a/erpnext/setup/doctype/authorization_control/test_authorization_control.py +++ b/erpnext/setup/doctype/authorization_control/test_authorization_control.py @@ -27,7 +27,7 @@ class TestAuthorizationControl(ERPNextTestSuite): } ).insert(ignore_permissions=True) - rule = frappe.get_doc( + frappe.get_doc( { "doctype": "Authorization Rule", "transaction": "Sales Order", @@ -37,19 +37,17 @@ class TestAuthorizationControl(ERPNextTestSuite): "approving_role": "_Test Approver Role", } ).insert() - self.addCleanup(frappe.delete_doc, "Authorization Rule", rule.name, force=1) controller = frappe.get_cached_doc("Authorization Control") - frappe.set_user(user) - self.addCleanup(frappe.set_user, "Administrator") # User lacks _Test Approver Role and the total exceeds the rule value -> not authorized. - self.assertRaises( - frappe.ValidationError, - controller.validate_approving_authority, - "Sales Order", - "_Test Company", - 5000, - ) + with self.set_user(user): + self.assertRaises( + frappe.ValidationError, + controller.validate_approving_authority, + "Sales Order", + "_Test Company", + 5000, + ) def test_get_value_based_rule_runs(self): # Exercises the four query-builder lookups (incl. the Employee designation subquery) added in diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index ea43ff9c373..7ddf8a6f4a5 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -161,12 +161,10 @@ class TestCompany(ERPNextTestSuite): } ) secondary.insert() - self.addCleanup(secondary.delete) primary = frappe.copy_doc(secondary) primary.is_primary_address = 1 primary.insert() - self.addCleanup(primary.delete) self.assertEqual(get_default_company_address(company), primary.name) @@ -236,12 +234,8 @@ class TestCompany(ERPNextTestSuite): company = "_Test Company" cd = frappe.qb.DocType("Company") - original = frappe.db.get_value("Company", company, "parent_company") # force '' (not NULL) at the SQL layer, bypassing frappe's empty -> NULL doc coercion frappe.qb.update(cd).set(cd.parent_company, "").where(cd.name == company).run() - self.addCleanup( - lambda: frappe.qb.update(cd).set(cd.parent_company, original).where(cd.name == company).run() - ) roots = {row.value for row in get_children("Company", parent="")} self.assertIn(company, roots) @@ -262,10 +256,8 @@ class TestCompany(ERPNextTestSuite): before = get_all_transactions_annual_history(company).get(key, 0) - quotation = make_quotation(company=company, transaction_date=txn_date, do_not_submit=True) - self.addCleanup(frappe.delete_doc, "Quotation", quotation.name, force=True) - sales_order = make_sales_order(company=company, transaction_date=txn_date, do_not_submit=True) - self.addCleanup(frappe.delete_doc, "Sales Order", sales_order.name, force=True) + make_quotation(company=company, transaction_date=txn_date, do_not_submit=True) + make_sales_order(company=company, transaction_date=txn_date, do_not_submit=True) after = get_all_transactions_annual_history(company).get(key, 0) self.assertEqual(after - before, 2) diff --git a/erpnext/stock/doctype/bin/test_bin.py b/erpnext/stock/doctype/bin/test_bin.py index c2ea54204f6..00a87d7084d 100644 --- a/erpnext/stock/doctype/bin/test_bin.py +++ b/erpnext/stock/doctype/bin/test_bin.py @@ -5,6 +5,7 @@ import frappe from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.utils import _create_bin +from erpnext.tests.assertions import assert_raises_with_savepoint from erpnext.tests.utils import ERPNextTestSuite @@ -19,10 +20,8 @@ class TestBin(ERPNextTestSuite): bin1.insert() bin2 = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse) - frappe.db.savepoint("dup_bin") - with self.assertRaises(frappe.UniqueValidationError): + with assert_raises_with_savepoint(self, frappe.UniqueValidationError): bin2.insert() - frappe.db.rollback(save_point="dup_bin") # preserve transaction in postgres # util method should handle it bin = _create_bin(item_code, warehouse) diff --git a/erpnext/stock/doctype/company_restriction/test_company_restriction.py b/erpnext/stock/doctype/company_restriction/test_company_restriction.py index 5f3062c353d..4b8f0c81b8e 100644 --- a/erpnext/stock/doctype/company_restriction/test_company_restriction.py +++ b/erpnext/stock/doctype/company_restriction/test_company_restriction.py @@ -108,25 +108,23 @@ class TestCompanyRestriction(ERPNextTestSuite): frappe.get_doc({"doctype": "User Permission", **permission}).insert(ignore_permissions=True) frappe.clear_cache(user=user) - frappe.set_user(user) - self.addCleanup(frappe.set_user, "Administrator") + with self.set_user(user): + results = party_query( + "Customer", + customer, + "name", + 0, + 20, + filters={"disabled": 0, "company": "_Test Company"}, + ) + self.assertIn(customer, [row[0] for row in results]) - results = party_query( - "Customer", - customer, - "name", - 0, - 20, - filters={"disabled": 0, "company": "_Test Company"}, - ) - self.assertIn(customer, [row[0] for row in results]) - - details = get_party_details( - party=customer, - party_type="Customer", - company="_Test Company", - ) - self.assertEqual(details.customer, customer) + details = get_party_details( + party=customer, + party_type="Customer", + company="_Test Company", + ) + self.assertEqual(details.customer, customer) def test_unrestricted_item_is_not_blocked(self): item = make_item() @@ -184,14 +182,12 @@ class TestCompanyRestriction(ERPNextTestSuite): permitted = frappe.get_meta("Customer").get_permitted_fieldnames(user=manager) self.assertIn("restrict_to_companies", permitted) - frappe.set_user(sales_user) - self.addCleanup(frappe.set_user, "Administrator") + with self.set_user(sales_user): + doc = frappe.get_doc("Customer", customer) + doc.restrict_to_companies = 0 + doc.set("allowed_companies", []) + doc.save() - doc = frappe.get_doc("Customer", customer) - doc.restrict_to_companies = 0 - doc.set("allowed_companies", []) - doc.save() - - doc.reload() - self.assertEqual(doc.restrict_to_companies, 1) - self.assertEqual([row.company for row in doc.allowed_companies], ["_Test Company"]) + doc.reload() + self.assertEqual(doc.restrict_to_companies, 1) + self.assertEqual([row.company for row in doc.allowed_companies], ["_Test Company"]) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index c8565e67cbd..24cfd9b2c7b 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -910,14 +910,8 @@ class TestDeliveryNote(ERPNextTestSuite): self.assertEqual(sn.warehouse, warehouse) def test_delivery_of_bundled_items_to_target_warehouse(self): - from erpnext.selling.doctype.customer.test_customer import create_internal_customer - company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") - customer_name = create_internal_customer( - customer_name="_Test Internal Customer 2", - represents_company="_Test Company with perpetual inventory", - allowed_to_interact_with="_Test Company with perpetual inventory", - ) + customer_name = "_Test Internal Customer 2" set_valuation_method("_Test Item", "FIFO") set_valuation_method("_Test Item Home Desktop 100", "FIFO") diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index e838fbbc743..4c9f5bae0e8 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -37,7 +37,7 @@ class TestDeliveryTrip(ERPNextTestSuite): "password": "test", "smtp_server": "localhost", "stmp_port": 25, - "email_id": "test@example.in", + "email_id": f"delivery-trip-{frappe.generate_hash(length=10)}@example.in", } ) outgoing.save() diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index dd287fa1b52..90bdf593cd7 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -443,6 +443,8 @@ class TestInventoryDimension(ERPNextTestSuite): document_type="Inv Site", validate_negative_stock=1, ) + inv_dimension.db_set("validate_negative_stock", 1) + frappe.clear_cache(doctype="Inventory Dimension") warehouse = create_warehouse("Negative Stock Warehouse") @@ -758,24 +760,13 @@ def create_inventory_dimension(**args): def prepare_data_for_internal_transfer(): - from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier - from erpnext.selling.doctype.customer.test_customer import create_internal_customer from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse company = "_Test Company with perpetual inventory" - customer = create_internal_customer( - "_Test Internal Customer 2", - company, - company, - ) - - supplier = create_internal_supplier( - "_Test Internal Supplier 2", - company, - company, - ) + customer = "_Test Internal Customer 2" + supplier = "_Test Internal Supplier 2" for store in ["Inter Transfer Store 1", "Inter Transfer Store 2", "Inter Transfer Store 3"]: if not frappe.db.exists("Store", store): diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index af003bd4b37..ff473c9b52c 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -25,7 +25,8 @@ from erpnext.stock.doctype.item.item import ( validate_is_stock_item, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -from erpnext.stock.get_item_details import get_item_details +from erpnext.stock.get_item_details import get_item_details, get_item_tax_map, get_item_tax_template +from erpnext.tests.assertions import assert_raises_with_savepoint from erpnext.tests.utils import ERPNextTestSuite @@ -303,28 +304,35 @@ class TestItem(ERPNextTestSuite): }, } - for data in expected_item_tax_template: - details = get_item_details( - frappe._dict( - { - "item_code": data["item_code"], - "tax_category": data["tax_category"], - "company": "_Test Company", - "price_list": "_Test Price List", - "currency": "_Test Currency", - "doctype": "Sales Order", - "conversion_rate": 1, - "price_list_currency": "_Test Currency", - "plc_conversion_rate": 1, - "order_type": "Sales", - "customer": "_Test Customer", - "conversion_factor": 1, - "price_list_uom_dependant": 1, - "ignore_pricing_rule": 1, - } - ) + for index, data in enumerate(expected_item_tax_template): + ctx = frappe._dict( + { + "item_code": data["item_code"], + "tax_category": data["tax_category"], + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Sales Order", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "order_type": "Sales", + "customer": "_Test Customer", + "conversion_factor": 1, + "price_list_uom_dependant": 1, + "ignore_pricing_rule": 1, + } ) + if index == 0: + details = get_item_details(ctx) + else: + details = frappe._dict() + get_item_tax_template(ctx, out=details) + details.item_tax_rate = get_item_tax_map( + doc=ctx, tax_template=details.item_tax_template, as_json=True + ) + self.assertEqual(details.item_tax_template, data["item_tax_template"]) self.assertEqual( json.loads(details.item_tax_rate), expected_item_tax_map[details.item_tax_template] @@ -488,17 +496,6 @@ class TestItem(ERPNextTestSuite): row.attribute_value = "Larger" break - def restore_test_size_large(): - doc = frappe.get_doc("Item Attribute", "Test Size") - for row in doc.item_attribute_values: - if row.attribute_value == "Larger": - row.attribute_value = "Large" - break - frappe.flags.attribute_values = None - doc.save() - - self.addCleanup(restore_test_size_large) - frappe.flags.attribute_values = None attribute.save() @@ -522,16 +519,6 @@ class TestItem(ERPNextTestSuite): small_variant.save() attribute = frappe.get_doc("Item Attribute", "Test Size") - original_values = {row.name: row.attribute_value for row in attribute.item_attribute_values} - - def restore_test_size_values(): - doc = frappe.get_doc("Item Attribute", "Test Size") - for row in doc.item_attribute_values: - row.attribute_value = original_values[row.name] - frappe.flags.attribute_values = None - doc.save() - - self.addCleanup(restore_test_size_values) for row in attribute.item_attribute_values: if row.attribute_value == "Large": @@ -572,18 +559,6 @@ class TestItem(ERPNextTestSuite): row.abbr = "LRG" break - def restore_test_size_abbr(): - doc = frappe.get_doc("Item Attribute", "Test Size") - for row in doc.item_attribute_values: - if row.attribute_value == "Large": - row.abbr = "L" - break - frappe.flags.attribute_values = None - doc.save() - - self.addCleanup(restore_test_size_abbr) - self.addCleanup(lambda: frappe.delete_doc_if_exists("Item", "_Test Variant Item-LRG", force=1)) - frappe.flags.attribute_values = None attribute.save() @@ -615,7 +590,6 @@ class TestItem(ERPNextTestSuite): } ) template.insert() - self.addCleanup(lambda: frappe.delete_doc_if_exists("Item", "_Test Variant Item Diff", force=1)) variant = create_variant("_Test Variant Item Diff", {"Test Size": "Large"}) variant.save() @@ -632,18 +606,6 @@ class TestItem(ERPNextTestSuite): row.abbr = "LRG" break - def restore_test_size_abbr(): - doc = frappe.get_doc("Item Attribute", "Test Size") - for row in doc.item_attribute_values: - if row.attribute_value == "Large": - row.abbr = "L" - break - frappe.flags.attribute_values = None - doc.save() - - self.addCleanup(restore_test_size_abbr) - self.addCleanup(lambda: frappe.delete_doc_if_exists("Item", "_Test Variant Item Diff-LRG", force=1)) - frappe.flags.attribute_values = None attribute.save() @@ -934,9 +896,8 @@ class TestItem(ERPNextTestSuite): item_doc = frappe.get_doc("Item", item_code) new_barcode = item_doc.append("barcodes") new_barcode.update(barcode_properties_list[0]) - frappe.db.savepoint("dup_barcode") - self.assertRaises(frappe.UniqueValidationError, item_doc.save) - frappe.db.rollback(save_point="dup_barcode") # preserve transaction in postgres + with assert_raises_with_savepoint(self, frappe.UniqueValidationError): + item_doc.save() # Add invalid barcode - should cause InvalidBarcode item_doc = frappe.get_doc("Item", item_code) @@ -1260,13 +1221,13 @@ class TestItem(ERPNextTestSuite): items = { "Test Opening Stock for Serial No": { "has_serial_no": 1, - "opening_stock": 5, + "opening_stock": 1, "serial_no_series": "SN-TOPN-.####", "valuation_rate": 100, }, "Test Opening Stock for Batch No": { "has_batch_no": 1, - "opening_stock": 5, + "opening_stock": 1, "batch_number_series": "BCH-TOPN-.####", "valuation_rate": 100, "create_new_batch": 1, @@ -1274,7 +1235,7 @@ class TestItem(ERPNextTestSuite): "Test Opening Stock for Serial and Batch No": { "has_serial_no": 1, "has_batch_no": 1, - "opening_stock": 5, + "opening_stock": 1, "batch_number_series": "SN-BCH-TOPN-.####", "serial_no_series": "BCH-SN-TOPN-.####", "valuation_rate": 100, diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py index 2d45e94a4fc..14658420892 100644 --- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py @@ -40,7 +40,6 @@ class TestItemAttribute(ERPNextTestSuite): frappe.delete_doc_if_exists("Item", "_Test Variant Item-L", force=1) variant = create_variant("_Test Variant Item", {"Test Size": "Large"}) variant.save() - self.addCleanup(frappe.delete_doc_if_exists, "Item", "_Test Variant Item-L", force=1) attribute = frappe.get_doc("Item Attribute", "Test Size") attribute.item_attribute_values = [] diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 48dd8bc91dd..3eb8569454b 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -3,6 +3,7 @@ import copy +from unittest.mock import patch import frappe from frappe.utils import add_days, add_to_date, flt, now, nowtime, today @@ -33,22 +34,12 @@ class TestLandedCostVoucher(ERPNextTestSuite): from erpnext.stock.doctype.landed_cost_voucher.landed_cost_voucher import get_vendor_invoices pi = make_purchase_invoice(item_code="_Test Non Stock Item", qty=1, rate=100) - self.addCleanup(self._cancel_and_delete_pi, pi.name) rows = get_vendor_invoices( "Purchase Invoice", "", "name", 0, 20, {"company": "_Test Company", "name": pi.name} ) self.assertTrue(any(r[0] == pi.name for r in rows)) - @staticmethod - def _cancel_and_delete_pi(name): - if not frappe.db.exists("Purchase Invoice", name): - return - doc = frappe.get_doc("Purchase Invoice", name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc("Purchase Invoice", name, force=1) - def test_landed_cost_voucher(self): frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) @@ -1333,6 +1324,7 @@ class TestLandedCostVoucher(ERPNextTestSuite): self.assertFalse(gl_entries) + @patch.dict(frappe.flags, {"dont_execute_stock_reposts": True}) def test_landed_cost_voucher_does_not_change_qty_across_stock_reco(self): """LCV cost updates must not change quantity after a batch stock reconciliation.""" from erpnext.stock.doctype.item.test_item import make_item @@ -1348,10 +1340,6 @@ class TestLandedCostVoucher(ERPNextTestSuite): first_batch = frappe.get_doc({"doctype": "Batch", "item": item}).insert().name second_batch = frappe.get_doc({"doctype": "Batch", "item": item}).insert().name - # Inspect the immediate LCV result before a queued repost repairs it. - frappe.flags.dont_execute_stock_reposts = True - self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts", None) - receipt = make_purchase_receipt( company=company, warehouse=warehouse, @@ -1603,35 +1591,16 @@ class TestLandedCostVoucherAccountingDimensions(ERPNextTestSuite): dimension = frappe.get_doc("Accounting Dimension", name) row = next((d for d in dimension.dimension_defaults if d.company == self.company), None) - if row: - previous = (row.mandatory_for_pl, row.mandatory_for_bs) - self.addCleanup(self.restore_dimension_default, name, previous) - else: + if not row: row = dimension.append( "dimension_defaults", {"company": self.company, "reference_document": dimension.document_type}, ) - self.addCleanup(self.remove_dimension_default, name) row.mandatory_for_pl = mandatory_for_pl row.mandatory_for_bs = mandatory_for_bs dimension.save() - def restore_dimension_default(self, name, previous): - dimension = frappe.get_doc("Accounting Dimension", name) - for row in dimension.dimension_defaults: - if row.company == self.company: - row.mandatory_for_pl, row.mandatory_for_bs = previous - dimension.save() - - def remove_dimension_default(self, name): - dimension = frappe.get_doc("Accounting Dimension", name) - dimension.set( - "dimension_defaults", - [d for d in dimension.dimension_defaults if d.company != self.company], - ) - dimension.save() - # tests def test_charge_row_dimension_reaches_gl_entry(self): diff --git a/erpnext/stock/doctype/packed_item/test_packed_item.py b/erpnext/stock/doctype/packed_item/test_packed_item.py index 5a91978e9d9..812596dd1e6 100644 --- a/erpnext/stock/doctype/packed_item/test_packed_item.py +++ b/erpnext/stock/doctype/packed_item/test_packed_item.py @@ -232,7 +232,6 @@ class TestPackedItem(ERPNextTestSuite): # a disabled version is rejected frappe.db.set_value("Product Bundle", version, "disabled", 1) - self.addCleanup(frappe.db.set_value, "Product Bundle", version, "disabled", 0) self.assertRaises( frappe.ValidationError, get_items_from_product_bundle, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index f3af2f4aa4a..5e1e8b1c3e2 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -29,6 +29,7 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle ) from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.get_item_details import get_conversion_factor +from erpnext.tests.assertions import assert_raises_with_savepoint from erpnext.tests.utils import ERPNextTestSuite @@ -1215,9 +1216,6 @@ class TestPurchaseReceipt(ERPNextTestSuite): company="_Test Company with perpetual inventory", ) - if not frappe.db.exists("Location", "Test Location"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() - pr = make_purchase_receipt( cost_center=cost_center, company="_Test Company with perpetual inventory", @@ -1240,9 +1238,6 @@ class TestPurchaseReceipt(ERPNextTestSuite): pr.cancel() def test_purchase_receipt_cost_center_with_balance_sheet_account(self): - if not frappe.db.exists("Location", "Test Location"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() - pr = make_purchase_receipt( company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", @@ -5684,8 +5679,7 @@ class TestPurchaseReceipt(ERPNextTestSuite): item_code = create_item("Test Item for PR against Rejected Qty").name warehouse = "_Test Warehouse - _TC" - company = frappe.db.get_value("Warehouse", warehouse, "company") - rejected_wh = create_warehouse("_Test Rejected Warehouse", company=company) + rejected_wh = "_Test Rejected Warehouse - _TC" pr = make_purchase_receipt( item_code=item_code, @@ -6537,11 +6531,9 @@ class TestPurchaseReceipt(ERPNextTestSuite): sle_before = frappe.db.count("Stock Ledger Entry", {"voucher_no": pr.name}) gle_before = frappe.db.count("GL Entry", {"voucher_no": pr.name}) - frappe.db.savepoint("before_blocked_cancel") - with self.assertRaises(frappe.LinkExistsError) as cm: + with assert_raises_with_savepoint(self, frappe.LinkExistsError) as cm: pr.cancel() self.assertIn(pi.name, str(cm.exception)) - frappe.db.rollback(save_point="before_blocked_cancel") # mimic the request-level rollback pr.reload() self.assertEqual(pr.docstatus, 1) @@ -6573,23 +6565,8 @@ def create_asset_category_for_pr_test(): def prepare_data_for_internal_transfer(): - from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier - from erpnext.selling.doctype.customer.test_customer import create_internal_customer - company = "_Test Company with perpetual inventory" - create_internal_customer( - "_Test Internal Customer 2", - company, - company, - ) - - create_internal_supplier( - "_Test Internal Supplier 2", - company, - company, - ) - if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"): account = "Unrealized Profit and Loss - TCP1" if not frappe.db.exists("Account", account): @@ -6720,9 +6697,6 @@ def get_items(**args): def make_purchase_receipt(**args): - if not frappe.db.exists("Location", "Test Location"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() - frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) pr = frappe.new_doc("Purchase Receipt") args = frappe._dict(args) diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index d4aed819a1c..e3b44be2855 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -528,8 +528,6 @@ class TestQualityInspection(ERPNextTestSuite): """Submitting a QI with reference_type 'Job Card' writes its name onto the Job Card's quality_inspection field (the Job Card branch of QualityInspection.update_qc_reference).""" - create_item("_Test Item") - # Job Card whose production_item matches the QI item_code -> must be updated. matching_jc = make_minimal_job_card(production_item="_Test Item") # Job Card with a different production_item -> the production_item filter must @@ -554,7 +552,6 @@ class TestQualityInspection(ERPNextTestSuite): def test_qi_job_card_reference_respects_production_item(self): """A QI referencing a Job Card by name but whose item_code does not match the Job Card's production_item must NOT update that Job Card.""" - create_item("_Test Item") mismatch_item = create_item("_Test Item Mismatch QC " + frappe.utils.random_string(6)).name # Job Card produces a different item than the QI's item_code. diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index ba1003f1b12..57da344d3f0 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -7,6 +7,7 @@ from unittest.mock import MagicMock, call, patch import frappe from frappe.utils import add_days, add_to_date, now, nowdate, today +from erpnext.accounts import utils as accounts_utils from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.utils import repost_gle_for_stock_vouchers from erpnext.controllers.stock_controller import create_item_wise_repost_entries @@ -248,19 +249,13 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin): se.submit() return se + @patch.dict(frappe.flags, {"dont_execute_stock_reposts": True}) def test_backdated_manufacture_repost_skips_redundant_dependent(self): from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import ( execute_reposting_entry, ) - frappe.flags.dont_execute_stock_reposts = True - self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts", None) - - original_setting = frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting") frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 1) - self.addCleanup( - frappe.db.set_single_value, "Stock Reposting Settings", "item_based_reposting", original_setting - ) company = "_Test Company with perpetual inventory" source_wh = "Stores - TCP1" @@ -337,10 +332,8 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin): riv.set_status("Skipped") @ERPNextTestSuite.change_settings("Stock Reposting Settings", {"item_based_reposting": 0}) + @patch.dict(frappe.flags, {"dont_execute_stock_reposts": True}) def test_prevention_of_cancelled_transaction_riv(self): - frappe.flags.dont_execute_stock_reposts = True - self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts") - item = make_item() warehouse = "_Test Warehouse - _TC" old = make_stock_entry(item_code=item.name, to_warehouse=warehouse, qty=2, rate=5) @@ -374,15 +367,13 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin): from erpnext.stock.doctype.repost_item_valuation import repost_item_valuation as riv - orig_max_writes = frappe.db.MAX_WRITES_PER_TRANSACTION - self.addCleanup(setattr, frappe.db, "MAX_WRITES_PER_TRANSACTION", orig_max_writes) - def status_after(error): doc = frappe.new_doc("Repost Item Valuation") doc.name = "test-recoverable-riv" doc.set_status = doc.log_error = doc.db_set = MagicMock() captured = {} with ( + patch.object(frappe.db, "MAX_WRITES_PER_TRANSACTION", frappe.db.MAX_WRITES_PER_TRANSACTION), patch.object(frappe, "in_test", False), patch.object(frappe.db, "exists", return_value=True), patch.object(frappe.db, "commit"), @@ -397,14 +388,8 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin): self.assertEqual(status_after(QueryDeadlockError("deadlock detected")), "In Progress") self.assertEqual(status_after(ValueError("boom")), "Failed") + @patch.object(accounts_utils, "GL_REPOSTING_CHUNK", 1) def test_gl_repost_progress(self): - from erpnext.accounts import utils - - # lower numbers to simplify test - orig_chunk_size = utils.GL_REPOSTING_CHUNK - utils.GL_REPOSTING_CHUNK = 1 - self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size) - doc = frappe.new_doc("Repost Item Valuation") doc.db_set = MagicMock() @@ -425,14 +410,8 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin): self.assertNotIn(call("gl_reposting_index", 1), doc.db_set.mock_calls) + @patch.object(accounts_utils, "GL_REPOSTING_CHUNK", 2) def test_gl_complete_gl_reposting(self): - from erpnext.accounts import utils - - # lower numbers to simplify test - orig_chunk_size = utils.GL_REPOSTING_CHUNK - utils.GL_REPOSTING_CHUNK = 2 - self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size) - item = self.make_item().name company = "_Test Company with perpetual inventory" @@ -471,14 +450,8 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin): gle_filters={"account": "Stock In Hand - TCP1"}, ) + @patch.object(accounts_utils, "GL_REPOSTING_CHUNK", 2) def test_duplicate_ple_on_repost(self): - from erpnext.accounts import utils - - # lower numbers to simplify test - orig_chunk_size = utils.GL_REPOSTING_CHUNK - utils.GL_REPOSTING_CHUNK = 2 - self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size) - rate = 100 item = self.make_item() item.valuation_rate = 90 diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index 44d2e1dfb6b..af415161135 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -1465,9 +1465,7 @@ class TestSerialandBatchBundle(ERPNextTestSuite): def _allow_negative_stock_temporarily(self): for field in ("allow_negative_stock", "allow_negative_stock_for_batch"): - original = frappe.db.get_single_value("Stock Settings", field) frappe.db.set_single_value("Stock Settings", field, 1) - self.addCleanup(frappe.db.set_single_value, "Stock Settings", field, original) def _disable_negative_stock(self): frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 4171e06118a..db0a92ab8a4 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -2,7 +2,7 @@ # See license.txt import json -import time +from unittest.mock import patch from uuid import uuid4 import frappe @@ -1129,12 +1129,10 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): # original amount self.assertEqual(50, _get_stock_credit(final_consumption)) + @patch.dict(frappe.flags, {"dont_execute_stock_reposts": True}) def test_tie_breaking(self): from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_entries - frappe.flags.dont_execute_stock_reposts = True - self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts") - item = make_item().name warehouse = "_Test Warehouse - _TC" @@ -1237,8 +1235,6 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): posting_time="02:00:00", ) - time.sleep(3) - reciept2 = make_stock_entry( item_code=item, to_warehouse=warehouse, @@ -1277,8 +1273,6 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): posting_time="02:00:00", ) - time.sleep(3) - # backdated entry with same timestamp but different ms part reciept2 = make_stock_entry( item_code=item, @@ -1323,7 +1317,6 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): posting_date="2021-01-01", posting_time="02:00:00", ) - time.sleep(1) receipt2 = make_purchase_receipt( item_code=item, warehouse=warehouse, @@ -1380,7 +1373,7 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): ) dns = [] - for i in range(5): + for i in range(3): dns.append( create_delivery_note( item_code=item, @@ -1391,19 +1384,17 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): posting_time=posting_time, ) ) - time.sleep(1) - - dn = dns[2] + dn = dns[1] dn.cancel() - expected_qty_after_transaction_of_dns3 = 40 - qty_after_transaction_of_dns3 = frappe.db.get_value( + expected_qty_after_transaction = 60 + qty_after_transaction = frappe.db.get_value( "Stock Ledger Entry", - {"voucher_no": dns[3].name, "is_cancelled": 0}, + {"voucher_no": dns[2].name, "is_cancelled": 0}, "qty_after_transaction", ) - self.assertEqual(expected_qty_after_transaction_of_dns3, qty_after_transaction_of_dns3) + self.assertEqual(expected_qty_after_transaction, qty_after_transaction) def test_get_next_stock_reco_respects_creation_order(self): # A stock reco sharing the exact posting timestamp of the current entry must only count as the diff --git a/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py index 7290c658d1f..60263b4295c 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py +++ b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py @@ -17,10 +17,6 @@ TEST_WAREHOUSE = "_Test Warehouse - _TC" class TestStockRepostingSettings(ERPNextTestSuite): - def tearDown(self): - frappe.db.set_single_value("Stock Reposting Settings", "repost_incorrect_valuation_entries", 0) - super().tearDown() - def test_auto_repost_disabled_does_nothing(self): frappe.db.set_single_value("Stock Reposting Settings", "repost_incorrect_valuation_entries", 0) with patch("frappe.enqueue") as enqueue: diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index 1c863852b7a..31eabc2af31 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -24,8 +24,16 @@ from erpnext.tests.utils import ERPNextTestSuite class TestStockReservationEntry(ERPNextTestSuite): def setUp(self) -> None: self.warehouse = "_Test Warehouse - _TC" - self.sr_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}) - create_material_receipt(items={self.sr_item.name: self.sr_item}, warehouse=self.warehouse, qty=100) + self._sr_item = None + + @property + def sr_item(self): + if self._sr_item is None: + self._sr_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}) + create_material_receipt( + items={self._sr_item.name: self._sr_item}, warehouse=self.warehouse, qty=100 + ) + return self._sr_item @ERPNextTestSuite.change_settings("Stock Settings", {"allow_negative_stock": 0}) def test_validate_stock_reservation_settings(self) -> None: @@ -192,9 +200,9 @@ class TestStockReservationEntry(ERPNextTestSuite): { "item_code": item_code, "warehouse": self.warehouse, - "qty": randint(11, 100), + "qty": 20, "uom": properties.stock_uom, - "rate": randint(10, 400), + "rate": 100, } ) @@ -225,7 +233,7 @@ class TestStockReservationEntry(ERPNextTestSuite): se.cancel() # Test - 3: Stock should be fully Reserved if the Available Qty to Reserve is greater than the Un-reserved Qty. - create_material_receipt(items_details, self.warehouse, qty=110) + create_material_receipt(items_details, self.warehouse, qty=25) so.create_stock_reservation_entries() so.load_from_db() @@ -262,9 +270,6 @@ class TestStockReservationEntry(ERPNextTestSuite): do_not_submit=True, ) - for row in so.items: - row.qty = 80 - so.save() so.submit() so.create_stock_reservation_entries() @@ -296,7 +301,7 @@ class TestStockReservationEntry(ERPNextTestSuite): dn2 = make_delivery_note(so.name) for item in dn2.items: - item.qty = 70 + item.qty = 15 dn2.save() dn2.submit() @@ -608,7 +613,7 @@ class TestStockReservationEntry(ERPNextTestSuite): ) def test_auto_reserve_serial_and_batch(self) -> None: items_details = create_items() - create_material_receipt(items_details, self.warehouse, qty=100) + create_material_receipt(items_details, self.warehouse, qty=2) item_list = [] for item_code, properties in items_details.items(): @@ -616,9 +621,9 @@ class TestStockReservationEntry(ERPNextTestSuite): { "item_code": item_code, "warehouse": self.warehouse, - "qty": randint(11, 100), + "qty": 2, "uom": properties.stock_uom, - "rate": randint(10, 400), + "rate": 100, } ) diff --git a/erpnext/stock/report/available_batch_report/test_available_batch_report.py b/erpnext/stock/report/available_batch_report/test_available_batch_report.py index 9de21bc9818..a473a38b561 100644 --- a/erpnext/stock/report/available_batch_report/test_available_batch_report.py +++ b/erpnext/stock/report/available_batch_report/test_available_batch_report.py @@ -8,15 +8,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestAvailableBatchReport(ERPNextTestSuite): - @staticmethod - def _cancel_and_delete_stock_entry(name): - if not frappe.db.exists("Stock Entry", name): - return - doc = frappe.get_doc("Stock Entry", name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc("Stock Entry", name, force=1) - def test_report_runs_and_lists_batch_qty(self): # The report selects Batch columns (expiry_date, and item_name when show_item_name is set) # while grouping by SLE columns; the Batch PK must be in the GROUP BY for the report to run @@ -35,9 +26,6 @@ class TestAvailableBatchReport(ERPNextTestSuite): se = make_stock_entry( item_code=item, target="_Test Warehouse - _TC", qty=7, basic_rate=10, purpose="Material Receipt" ) - # make_item is idempotent (returns the existing item), but each receipt stacks a new batch, - # so cancel+delete the stock entry to keep repeated runs clean. - self.addCleanup(self._cancel_and_delete_stock_entry, se.name) batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) filters = frappe._dict(to_date=today(), item_code=item, show_item_name=1) diff --git a/erpnext/stock/report/serial_and_batch_summary/test_serial_and_batch_summary.py b/erpnext/stock/report/serial_and_batch_summary/test_serial_and_batch_summary.py index a5846590393..ef4fdc26420 100644 --- a/erpnext/stock/report/serial_and_batch_summary/test_serial_and_batch_summary.py +++ b/erpnext/stock/report/serial_and_batch_summary/test_serial_and_batch_summary.py @@ -12,21 +12,11 @@ class TestSerialAndBatchSummary(ERPNextTestSuite): return execute(frappe._dict(extra))[1] - @staticmethod - def _cancel_and_delete_stock_entry(name): - if not frappe.db.exists("Stock Entry", name): - return - doc = frappe.get_doc("Stock Entry", name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc("Stock Entry", name, force=1) - def test_serial_receipt_listed(self): from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry item = "_Test Serialized Item With Series" se = make_stock_entry(item_code=item, to_warehouse="Stores - _TC", qty=3, basic_rate=100) - self.addCleanup(self._cancel_and_delete_stock_entry, se.name) data = self.run_report(voucher_no=[se.name], voucher_type="Stock Entry") @@ -55,7 +45,6 @@ class TestSerialAndBatchSummary(ERPNextTestSuite): } ).name se = make_stock_entry(item_code=item, to_warehouse="_Test Warehouse - _TC", qty=10, basic_rate=50) - self.addCleanup(self._cancel_and_delete_stock_entry, se.name) batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) data = self.run_report(voucher_no=[se.name], voucher_type="Stock Entry") diff --git a/erpnext/stock/report/stock_balance/test_stock_balance.py b/erpnext/stock/report/stock_balance/test_stock_balance.py index 946cd518260..1589ab6a0e1 100644 --- a/erpnext/stock/report/stock_balance/test_stock_balance.py +++ b/erpnext/stock/report/stock_balance/test_stock_balance.py @@ -230,11 +230,10 @@ class TestStockBalance(ERPNextTestSuite): def test_alt_uom_balance_uses_first_alternate_uom(self): """When an item has multiple alt UOMs, only the first (lowest idx) is shown.""" - frappe.get_doc({"doctype": "UOM", "uom_name": "Carton"}).insert(ignore_if_duplicate=True) item = self.make_alt_uom_item( uoms=[ {"conversion_factor": 12, "uom": "Box"}, - {"conversion_factor": 144, "uom": "Carton"}, + {"conversion_factor": 144, "uom": "_Test UOM 1"}, ] ) diff --git a/erpnext/stock/tests/test_stock_ledger.py b/erpnext/stock/tests/test_stock_ledger.py index a47e348c803..61d93f3de95 100644 --- a/erpnext/stock/tests/test_stock_ledger.py +++ b/erpnext/stock/tests/test_stock_ledger.py @@ -17,8 +17,6 @@ class TestStockLedgerConversions(ERPNextTestSuite): item = make_item("_Test SL Cancel Item", {"is_stock_item": 1}).name se = make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=5, basic_rate=100) - # register cleanup before the assertions so the entry is removed even if one fails - self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name) self.assertTrue(frappe.db.exists("Stock Ledger Entry", {"voucher_no": se.name, "is_cancelled": 0})) @@ -35,8 +33,7 @@ class TestStockLedgerConversions(ERPNextTestSuite): from erpnext.stock.stock_ledger import get_valuation_rate item = make_item("_Test SL Valuation Item", {"is_stock_item": 1}).name - se = make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=10, basic_rate=250) - self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name) + make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=10, basic_rate=250) rate = get_valuation_rate(item, "_Test Warehouse - _TC", "Stock Entry", "_TEST-NO-SUCH-VOUCHER") self.assertEqual(rate, 250) @@ -55,12 +52,3 @@ class TestStockLedgerConversions(ERPNextTestSuite): "posting_datetime": now_datetime(), } self.assertIsInstance(get_future_sle_with_negative_qty(args), list | tuple) - - @staticmethod - def _cancel_and_delete(doctype, name): - if not frappe.db.exists(doctype, name): - return - doc = frappe.get_doc(doctype, name) - if doc.docstatus == 1: - doc.cancel() - frappe.delete_doc(doctype, name, force=1) diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py index 9403550482b..a8f7d402d64 100644 --- a/erpnext/support/doctype/issue/test_issue.py +++ b/erpnext/support/doctype/issue/test_issue.py @@ -28,7 +28,6 @@ class TestIssue(TestSetUp): creation = get_datetime("2019-03-04 12:00") # make issue with customer specific SLA - create_customer("_Test Customer", "__Test SLA Customer Group", "__Test SLA Territory") issue = make_issue(creation, "_Test Customer", 1) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index e00276dae30..f15a267d3f2 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -519,20 +519,7 @@ def create_service_level_agreement( def create_customer(): - customer = frappe.get_doc( - { - "doctype": "Customer", - "customer_name": "_Test Customer", - "customer_group": "Commercial", - "customer_type": "Individual", - "territory": "Rest Of The World", - } - ) - if not frappe.db.exists("Customer", "_Test Customer"): - customer.insert(ignore_permissions=True) - return customer.name - else: - return frappe.db.exists("Customer", "_Test Customer") + return "_Test Customer" def create_customer_group(): diff --git a/erpnext/templates/pages/test_partners.py b/erpnext/templates/pages/test_partners.py index 7b4b76d41db..989597e41dd 100644 --- a/erpnext/templates/pages/test_partners.py +++ b/erpnext/templates/pages/test_partners.py @@ -8,27 +8,16 @@ from erpnext.tests.utils import ERPNextTestSuite class TestPartnersPage(ERPNextTestSuite): - def _make_partner(self, name, show_in_website): - if not frappe.db.exists("Sales Partner", name): - frappe.get_doc( - { - "doctype": "Sales Partner", - "partner_name": name, - "territory": "_Test Territory", - "commission_rate": 5, - "show_in_website": show_in_website, - } - ).insert(ignore_permissions=True) - return name - def test_get_context_lists_only_website_partners(self): """partners.py builds the /partners list via frappe.get_all("Sales Partner", filters={"show_in_website": 1}, ...). Seed one website-visible partner and one hidden control partner, then assert the returned context contains the visible one and excludes the hidden one -- real membership of the converted query's result, not a tautology.""" - visible = self._make_partner("_Test Website Sales Partner", 1) - hidden = self._make_partner("_Test Hidden Sales Partner", 0) + visible = "_Test Sales Partner India - 1" + hidden = "_Test Sales Partner India - 2" + frappe.db.set_value("Sales Partner", visible, "show_in_website", 1) + frappe.db.set_value("Sales Partner", hidden, "show_in_website", 0) result = get_context(frappe._dict()) diff --git a/erpnext/tests/assertions.py b/erpnext/tests/assertions.py new file mode 100644 index 00000000000..98b7bd90dd2 --- /dev/null +++ b/erpnext/tests/assertions.py @@ -0,0 +1,24 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +from contextlib import contextmanager +from types import SimpleNamespace + +from frappe.database import savepoint + + +@contextmanager +def assert_raises_with_savepoint(test_case, expected_exception): + """Assert an exception while keeping the surrounding test transaction usable.""" + context = SimpleNamespace(exception=None) + with savepoint(): + try: + yield context + except Exception as exception: + context.exception = exception + raise + + if context.exception is None: + test_case.fail(f"{expected_exception.__name__} not raised") + if not isinstance(context.exception, expected_exception): + raise context.exception diff --git a/erpnext/tests/bootstrap_test_data.py b/erpnext/tests/bootstrap_test_data.py index 713c0bdf564..139b0537938 100644 --- a/erpnext/tests/bootstrap_test_data.py +++ b/erpnext/tests/bootstrap_test_data.py @@ -1,3 +1,4 @@ -# This file is solely to trigger BootStrapTestData from CI -# utils.py module import instantiates BootStrapTestData -from erpnext.tests.utils import ERPNextTestSuite +# This file is solely to bootstrap shared test data from CI. +from erpnext.tests.utils import bootstrap_test_data + +bootstrap_test_data() diff --git a/erpnext/tests/test_utils.py b/erpnext/tests/test_utils.py new file mode 100644 index 00000000000..dcb23fffef1 --- /dev/null +++ b/erpnext/tests/test_utils.py @@ -0,0 +1,88 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import unittest +from unittest.mock import patch + +import frappe + +from erpnext.tests.utils import ( + BootstrapTestData, + ERPNextTestSuite, + change_settings, + if_lending_app_installed, + if_lending_app_not_installed, +) + + +class TestERPNextTestUtils(ERPNextTestSuite): + def test_make_records_reuses_item_price_when_rate_changes(self): + fixture = BootstrapTestData.__new__(BootstrapTestData) + filters = {"item_code": "_Test Item", "price_list": "_Test Price List Rest of the World"} + item_price = frappe.db.get_value("Item Price", filters, "name") + self.assertIsNotNone(item_price) + frappe.db.set_value("Item Price", item_price, "price_list_rate", 999) + + fixture.make_item_price() + + self.assertEqual(frappe.db.count("Item Price", filters), 1) + self.assertEqual(frappe.db.get_value("Item Price", filters, "price_list_rate"), 10) + + def test_make_custom_doctype_repairs_each_missing_doctype(self): + fixture = BootstrapTestData.__new__(BootstrapTestData) + existing_doctypes = {"Shelf", "Rack", "Pallet", "Inv Site"} + + with ( + patch.object( + frappe.db, + "exists", + side_effect=lambda doctype, name: doctype == "DocType" and name in existing_doctypes, + ), + patch("erpnext.tests.utils.frappe.get_doc") as get_doc, + ): + fixture.make_custom_doctype() + + created_doctypes = [call.args[0]["name"] for call in get_doc.call_args_list] + self.assertCountEqual(created_doctypes, ["Store", "Order Assignment"]) + + def test_change_settings_restores_values_after_error(self): + original = frappe.db.get_single_value("Stock Settings", "auto_indent") + changed = 0 if original else 1 + + with self.assertRaisesRegex(RuntimeError, "expected failure"): + with change_settings("Stock Settings", auto_indent=changed): + self.assertEqual(frappe.db.get_single_value("Stock Settings", "auto_indent"), changed) + raise RuntimeError("expected failure") + + self.assertEqual(frappe.db.get_single_value("Stock Settings", "auto_indent"), original) + + def test_lending_decorators_preserve_names_and_skip(self): + with patch("erpnext.tests.utils.frappe.get_installed_apps", return_value=[]): + + @if_lending_app_installed + def requires_lending(): + return True + + @if_lending_app_not_installed + def excludes_lending(): + return True + + self.assertEqual(requires_lending.__name__, "requires_lending") + self.assertEqual(excludes_lending.__name__, "excludes_lending") + with self.assertRaises(unittest.SkipTest): + requires_lending() + self.assertTrue(excludes_lending()) + + with patch("erpnext.tests.utils.frappe.get_installed_apps", return_value=["lending"]): + + @if_lending_app_installed + def requires_lending(): + return True + + @if_lending_app_not_installed + def excludes_lending(): + return True + + self.assertTrue(requires_lending()) + with self.assertRaises(unittest.SkipTest): + excludes_lending() diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 1d5e12846ae..4f3b4c599a6 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -1,19 +1,20 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +import copy import unittest from contextlib import contextmanager from typing import Any, NewType import frappe -from frappe import _ from frappe.core.doctype.report.report import get_report_module_dotted_path from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.tests.utils import load_test_records_for -from frappe.utils import now_datetime, today +from frappe.utils import compare, now_datetime, today ReportFilters = dict[str, Any] ReportName = NewType("ReportName", str) +_test_data_bootstrapped = False def execute_script_report( @@ -59,30 +60,20 @@ def execute_script_report( def if_lending_app_installed(function): """Decorator to check if lending app is installed""" - - def wrapper(*args, **kwargs): - if "lending" in frappe.get_installed_apps(): - return function(*args, **kwargs) - return - - return wrapper + return unittest.skipUnless("lending" in frappe.get_installed_apps(), "lending is not installed")(function) def if_lending_app_not_installed(function): """Decorator to check if lending app is not installed""" - - def wrapper(*args, **kwargs): - if "lending" not in frappe.get_installed_apps(): - return function(*args, **kwargs) - return - - return wrapper + return unittest.skipIf("lending" in frappe.get_installed_apps(), "lending is installed")(function) -class BootStrapTestData: +class BootstrapTestData: def __init__(self): - self.make_presets() - self.make_master_data() + lock_name = f"{frappe.local.site}:erpnext-test-data" + with frappe.db.advisory_lock(lock_name, timeout=300): + self.make_presets() + self.make_master_data() def make_presets(self): from frappe.desk.page.setup_wizard.install_fixtures import update_genders, update_salutations @@ -255,25 +246,56 @@ class BootStrapTestData: stock_settings.enable_serial_and_batch_no_for_item = 1 stock_settings.save() - def make_records(self, key, records): - doctype = records[0].get("doctype") + def make_records(self, key, records, update_fields=()): + """Create shared fixtures once and repair explicitly mutable values.""" + if not records: + return + if not key: + raise ValueError("make_records expects at least one identity field") - def get_filters(record): - filters = {} - for x in key: - filters[x] = record.get(x) - return filters + doctypes = {record.get("doctype") for record in records} + if len(doctypes) != 1 or None in doctypes: + raise ValueError("make_records expects records for exactly one DocType") - for x in records: - filters = get_filters(x) - if not frappe.db.exists(doctype, filters): - frappe.get_doc(x).insert() + doctype = doctypes.pop() + for record in records: + filters = {fieldname: record.get(fieldname) for fieldname in key} + if not any(value is not None for value in filters.values()): + raise ValueError(f"make_records expects an identity for {doctype}") + + if name := frappe.db.exists(doctype, filters): + self._update_fixture_values(doctype, name, record, update_fields) + else: + frappe.get_doc(record).insert(ignore_if_duplicate=True) + + @staticmethod + def _update_fixture_values(doctype, name, record, update_fields): + if not update_fields: + return + + doc = frappe.get_doc(doctype, name) + changed = False + for fieldname in update_fields: + if fieldname not in record: + continue + + expected = record[fieldname] + field = doc.meta.get_field(fieldname) + fieldtype = field.fieldtype if field else None + if compare(doc.get(fieldname), "=", expected, fieldtype): + continue + + doc.set(fieldname, expected) + changed = True + + if changed: + doc.save(ignore_permissions=True) def make_price_list(self): records = [ { "doctype": "Price List", - "price_list_name": _("Standard Buying"), + "price_list_name": "Standard Buying", "enabled": 1, "buying": 1, "selling": 0, @@ -281,7 +303,7 @@ class BootStrapTestData: }, { "doctype": "Price List", - "price_list_name": _("Standard Selling"), + "price_list_name": "Standard Selling", "enabled": 1, "buying": 0, "selling": 1, @@ -337,7 +359,11 @@ class BootStrapTestData: "selling": 0, }, ] - self.make_records(["price_list_name", "enabled", "selling", "buying", "currency"], records) + self.make_records( + ["price_list_name"], + records, + update_fields=("enabled", "selling", "buying", "currency", "price_not_uom_dependant"), + ) def make_monthly_distribution(self): records = [ @@ -441,7 +467,7 @@ class BootStrapTestData: "parent_department": "All Departments", }, ] - self.make_records(["department_name"], records) + self.make_records(["department_name", "company"], records) def make_role(self): records = [ @@ -590,7 +616,7 @@ class BootStrapTestData: "user_id": "test2@example.com", }, ] - self.make_records(["first_name"], records) + self.make_records(["user_id"], records) def make_sales_person(self): records = [ @@ -726,8 +752,11 @@ class BootStrapTestData: } ) - key = ["year_start_date", "year_end_date"] - self.make_records(key, records) + self.make_records( + ["year"], + records, + update_fields=("year_start_date", "year_end_date", "is_short_year"), + ) def make_payment_term(self): records = [ @@ -1915,7 +1944,7 @@ class BootStrapTestData: "company": "_Test Company", }, ] - self.make_records(["item_code", "item_name"], records) + self.make_records(["item_code"], records, update_fields=("item_name",)) def make_product_bundle(self): from erpnext.selling.doctype.product_bundle.product_bundle import get_active_product_bundle @@ -2544,7 +2573,7 @@ class BootStrapTestData: }, { "doctype": "Item Price", - "price_list": _("Standard Selling"), + "price_list": "Standard Selling", "item_code": "Loyal Item", "price_list_rate": 10000, }, @@ -2555,7 +2584,11 @@ class BootStrapTestData: "price_list_rate": 10000, }, ] - self.make_records(["item_code", "price_list", "price_list_rate"], records) + self.make_records( + ["item_code", "price_list", "customer", "supplier"], + records, + update_fields=("price_list_rate", "valid_from", "valid_upto", "uom", "packing_unit", "batch_no"), + ) def make_currency_exchange(self): """Seed current-dated USD<->INR rates so foreign-currency documents @@ -2587,7 +2620,16 @@ class BootStrapTestData: "for_selling": 1, }, ] - self.make_records(["from_currency", "to_currency", "date", "for_buying", "for_selling"], records) + identity_fields = ("from_currency", "to_currency", "for_buying", "for_selling") + for record in records: + filters = {fieldname: record.get(fieldname) for fieldname in identity_fields} + name = frappe.db.get_value("Currency Exchange", filters, "name", order_by="date desc") + if name: + self._update_fixture_values( + "Currency Exchange", name, record, update_fields=("date", "exchange_rate") + ) + else: + frappe.get_doc(record).insert(ignore_if_duplicate=True) def make_operation(self): records = [ @@ -2713,160 +2755,87 @@ class BootStrapTestData: self.make_records(["finance_book_name"], records) def make_custom_doctype(self): - if not frappe.db.exists("DocType", "Shelf"): - frappe.get_doc( - { - "doctype": "DocType", - "name": "Shelf", - "module": "Stock", - "custom": 1, - "naming_rule": "By fieldname", - "autoname": "field:shelf_name", - "fields": [{"label": "Shelf Name", "fieldname": "shelf_name", "fieldtype": "Data"}], - "permissions": [ - { - "role": "System Manager", - "permlevel": 0, - "read": 1, - "write": 1, - "create": 1, - "delete": 1, - } - ], - } - ).insert(ignore_permissions=True) + for doctype, fieldname, label in ( + ("Shelf", "shelf_name", "Shelf Name"), + ("Rack", "rack_name", "Rack Name"), + ("Pallet", "pallet_name", "Pallet Name"), + ("Inv Site", "site_name", "Site Name"), + ("Store", "store_name", "Store Name"), + ): + self._make_simple_custom_doctype(doctype, fieldname, label) - if not frappe.db.exists("DocType", "Rack"): - frappe.get_doc( - { - "doctype": "DocType", - "name": "Rack", - "module": "Stock", - "custom": 1, - "naming_rule": "By fieldname", - "autoname": "field:rack_name", - "fields": [{"label": "Rack Name", "fieldname": "rack_name", "fieldtype": "Data"}], - "permissions": [ - { - "role": "System Manager", - "permlevel": 0, - "read": 1, - "write": 1, - "create": 1, - "delete": 1, - } - ], - } - ).insert(ignore_permissions=True) + self._make_order_assignment_doctype() - if not frappe.db.exists("DocType", "Pallet"): - frappe.get_doc( - { - "doctype": "DocType", - "name": "Pallet", - "module": "Stock", - "custom": 1, - "naming_rule": "By fieldname", - "autoname": "field:pallet_name", - "fields": [{"label": "Pallet Name", "fieldname": "pallet_name", "fieldtype": "Data"}], - "permissions": [ - { - "role": "System Manager", - "permlevel": 0, - "read": 1, - "write": 1, - "create": 1, - "delete": 1, - } - ], - } - ).insert(ignore_permissions=True) + @staticmethod + def _make_simple_custom_doctype(doctype, fieldname, label): + if frappe.db.exists("DocType", doctype): + return - if not frappe.db.exists("DocType", "Inv Site"): - frappe.get_doc( - { - "doctype": "DocType", - "name": "Inv Site", - "module": "Stock", - "custom": 1, - "naming_rule": "By fieldname", - "autoname": "field:site_name", - "fields": [{"label": "Site Name", "fieldname": "site_name", "fieldtype": "Data"}], - "permissions": [ - { - "role": "System Manager", - "permlevel": 0, - "read": 1, - "write": 1, - "create": 1, - "delete": 1, - } - ], - } - ).insert(ignore_permissions=True) - - if not frappe.db.exists("DocType", "Store"): - frappe.get_doc( + frappe.get_doc( + { + "doctype": "DocType", + "name": doctype, + "module": "Stock", + "custom": 1, + "naming_rule": "By fieldname", + "autoname": f"field:{fieldname}", + "fields": [{"label": label, "fieldname": fieldname, "fieldtype": "Data"}], + "permissions": [ { - "doctype": "DocType", - "name": "Store", - "module": "Stock", - "custom": 1, - "naming_rule": "By fieldname", - "autoname": "field:store_name", - "fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}], - "permissions": [ - { - "role": "System Manager", - "permlevel": 0, - "read": 1, - "write": 1, - "create": 1, - "delete": 1, - } - ], + "role": "System Manager", + "permlevel": 0, + "read": 1, + "write": 1, + "create": 1, + "delete": 1, } - ).insert(ignore_permissions=True) + ], + } + ).insert(ignore_permissions=True, ignore_if_duplicate=True) - if not frappe.db.exists("DocType", "Order Assignment"): - frappe.get_doc( + @staticmethod + def _make_order_assignment_doctype(): + if frappe.db.exists("DocType", "Order Assignment"): + return + + frappe.get_doc( + { + "doctype": "DocType", + "name": "Order Assignment", + "module": "Buying", + "custom": 1, + "autoname": "field:po", + "fields": [ { - "doctype": "DocType", - "name": "Order Assignment", - "module": "Buying", - "custom": 1, - "autoname": "field:po", - "fields": [ - { - "label": "PO", - "fieldname": "po", - "fieldtype": "Link", - "options": "Purchase Order", - }, - { - "label": "Supplier", - "fieldname": "supplier", - "fieldtype": "Data", - "fetch_from": "po.supplier", - }, - ], - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1, - }, - {"read": 1, "role": "Supplier"}, - ], - } - ).insert(ignore_if_duplicate=True) + "label": "PO", + "fieldname": "po", + "fieldtype": "Link", + "options": "Purchase Order", + }, + { + "label": "Supplier", + "fieldname": "supplier", + "fieldtype": "Data", + "fetch_from": "po.supplier", + }, + ], + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1, + }, + {"read": 1, "role": "Supplier"}, + ], + } + ).insert(ignore_permissions=True, ignore_if_duplicate=True) def make_address(self): records = [ @@ -3046,7 +3015,21 @@ class BootStrapTestData: self.make_records(["store_name"], records) -BootStrapTestData() +# Keep the old spelling for test helpers in downstream apps. +BootStrapTestData = BootstrapTestData + + +def bootstrap_test_data(): + global _test_data_bootstrapped + if _test_data_bootstrapped: + return + + BootstrapTestData() + _test_data_bootstrapped = True + + +# Downstream apps create their fixtures while importing this module. +bootstrap_test_data() class ERPNextTestSuite(unittest.TestCase): @@ -3060,6 +3043,7 @@ class ERPNextTestSuite(unittest.TestCase): @classmethod def setUpClass(cls): + bootstrap_test_data() cls.globalTestRecords = {} def tearDown(self): @@ -3086,24 +3070,21 @@ class ERPNextTestSuite(unittest.TestCase): @ERPNextTestSuite.registerAs(staticmethod) @contextmanager def change_settings(doctype, settings_dict=None, /, **settings) -> None: - """Temporarily: change settings in a settings doctype.""" - import copy - + """Temporarily change fields in a settings DocType.""" if settings_dict is None: settings_dict = settings - settings = frappe.get_doc(doctype) - previous_settings = copy.deepcopy(settings_dict) - for key in previous_settings: - previous_settings[key] = getattr(settings, key) + settings_doc = frappe.get_doc(doctype) + previous_settings = {key: copy.deepcopy(settings_doc.get(key)) for key in settings_dict} for key, value in settings_dict.items(): - setattr(settings, key, value) - settings.save(ignore_permissions=True) + settings_doc.set(key, value) + settings_doc.save(ignore_permissions=True) - yield - - settings = frappe.get_doc(doctype) - for key, value in previous_settings.items(): - setattr(settings, key, value) - settings.save(ignore_permissions=True) + try: + yield + finally: + settings_doc = frappe.get_doc(doctype) + for key, value in previous_settings.items(): + settings_doc.set(key, value) + settings_doc.save(ignore_permissions=True)