test: stock balance report tests
diff --git a/erpnext/stock/report/stock_balance/test_stock_balance.py b/erpnext/stock/report/stock_balance/test_stock_balance.py
new file mode 100644
index 0000000..52c48a0
--- /dev/null
+++ b/erpnext/stock/report/stock_balance/test_stock_balance.py
@@ -0,0 +1,77 @@
+from typing import Any, Dict
+
+import frappe
+from frappe import _dict
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import today
+
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.report.stock_balance.stock_balance import execute
+
+
+def stock_balance(filters):
+ return list(map(_dict, execute(filters)[1]))
+
+
+class TestStockBalance(FrappeTestCase):
+ # ----------- utils
+
+ def setUp(self):
+ self.item = make_item()
+ self.filters = {
+ "company": "_Test Company",
+ "item_code": self.item.name,
+ "from_date": str(today()),
+ "to_date": str(today()),
+ }
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+ def assertPartialDictionary(self, expected: Dict[str, Any], actual: Dict[str, Any]):
+ for k, v in expected.items():
+ self.assertEqual(v, actual[k], msg=f"{expected=}\n{actual=}")
+
+ def generate_stock_ledger(self, item_code: str, movements):
+
+ for movement in map(_dict, movements):
+ make_stock_entry(
+ item_code=item_code,
+ **movement,
+ to_warehouse=movement.to_warehouse or "_Test Warehouse - _TC",
+ )
+
+ def assertBasicInvariants(self, rows):
+ for row in rows:
+ msg = f"Invariants not met for {rows=}"
+ # qty invariant
+ self.assertAlmostEqual(row.bal_qty, row.opening_qty + row.in_qty - row.out_qty, msg)
+
+ # value invariant
+ self.assertAlmostEqual(row.bal_val, row.opening_val + row.in_val - row.out_val, msg)
+
+ # ----------- tests
+
+ def test_basic_stock_balance(self):
+ """Check very basic functionality and item info"""
+ rows = stock_balance(self.filters)
+ self.assertEqual(rows, [])
+
+ self.generate_stock_ledger(self.item.name, [_dict(qty=5, rate=10)])
+
+ # check item info
+ rows = stock_balance(self.filters)
+ self.assertPartialDictionary(
+ {
+ "item_code": self.item.name,
+ "item_name": self.item.item_name,
+ "item_group": self.item.item_group,
+ "stock_uom": self.item.stock_uom,
+ "in_qty": 5,
+ "in_val": 50,
+ "val_rate": 10,
+ },
+ rows[0],
+ )
+ self.assertBasicInvariants(rows)