70 """Verify that GncPrice / GncPriceDB methods return properly wrapped
71 Python objects instead of raw SwigPyObjects."""
75 def test_lookup_latest_returns_gnc_price(self):
77 self.assertIsNotNone(price,
"No price found for TSTK/USD")
78 self.assertIsInstance(price, GncPrice)
80 def test_nth_price_returns_gnc_price(self):
82 self.assertIsNotNone(price,
"nth_price(TSTK, 0) returned None")
83 self.assertIsInstance(price, GncPrice)
87 def test_get_commodity_returns_gnc_commodity(self):
89 self.assertIsNotNone(price)
90 comm = price.get_commodity()
91 self.assertIsInstance(comm, GncCommodity)
93 def test_get_currency_returns_gnc_commodity(self):
95 self.assertIsNotNone(price)
96 curr = price.get_currency()
97 self.assertIsInstance(curr, GncCommodity)
99 def test_get_value_returns_gnc_numeric(self):
101 self.assertIsNotNone(price)
102 val = price.get_value()
103 self.assertIsInstance(val, GncNumeric)
105 def test_clone_returns_gnc_price(self):
107 self.assertIsNotNone(price)
108 cloned = price.clone(self.
book)
109 self.assertIsInstance(cloned, GncPrice)
113 def test_lookup_latest_any_currency_returns_list_of_gnc_price(self):
115 self.assertIsInstance(prices, list)
116 self.assertGreater(len(prices), 0,
117 "Expected at least one price for TSTK")
119 self.assertIsInstance(p, GncPrice)
121 def test_get_prices_returns_list_of_gnc_price(self):
123 self.assertIsInstance(prices, list)
124 self.assertGreater(len(prices), 0)
126 self.assertIsInstance(p, GncPrice)
128 def test_lookup_nearest_in_time_any_currency(self):
129 date = datetime(2025, 1, 20)
130 prices = self.
pricedb.lookup_nearest_in_time_any_currency_t64(
132 self.assertIsInstance(prices, list)
134 self.assertIsInstance(p, GncPrice)
136 def test_lookup_nearest_before_any_currency(self):
137 date = datetime(2025, 7, 1)
138 prices = self.
pricedb.lookup_nearest_before_any_currency_t64(
140 self.assertIsInstance(prices, list)
142 self.assertIsInstance(p, GncPrice)
149 """Verify that GncLot.get_split_list() returns wrapped Split objects.
151 Creates a buy+sell pair on an account then scrubs lots, matching the
152 pattern in test_account.py's test_assignlots.
157 self.
book = self.
ses.get_book()
158 table = self.
book.get_table()
159 currency = table.lookup(
"CURRENCY",
"USD")
161 stock = GncCommodity(self.
book,
"Lot Test",
"COMMODITY",
"LTX",
"LTX", 100000)
166 root = self.
book.get_root_account()
169 cash_acct = Account(self.
book)
170 cash_acct.SetCommodity(currency)
171 root.append_child(cash_acct)
173 tx = Transaction(self.
book)
175 tx.SetCurrency(currency)
176 tx.SetDateEnteredSecs(datetime.now())
177 tx.SetDatePostedSecs(datetime.now())
180 s1 = Split(self.
book)
183 s1.SetAmount(GncNumeric(13, 10))
184 s1.SetValue(GncNumeric(100, 1))
186 s2 = Split(self.
book)
188 s2.SetAccount(cash_acct)
189 s2.SetAmount(GncNumeric(-100, 1))
190 s2.SetValue(GncNumeric(-100, 1))
193 s3 = Split(self.
book)
196 s3.SetAmount(GncNumeric(-13, 10))
197 s3.SetValue(GncNumeric(-100, 1))
199 s4 = Split(self.
book)
201 s4.SetAccount(cash_acct)
202 s4.SetAmount(GncNumeric(100, 1))
203 s4.SetValue(GncNumeric(100, 1))
211 def test_lot_exists(self):
213 self.assertIsInstance(lots, list)
214 self.assertGreater(len(lots), 0,
"ScrubLots should have created a lot")
216 self.assertIsInstance(lot, GncLot)
218 def test_get_split_list_returns_splits(self):
220 self.assertGreater(len(lots), 0)
221 splits = lots[0].get_split_list()
222 self.assertIsInstance(splits, list)
223 self.assertGreater(len(splits), 0,
"Lot has no splits")
225 self.assertIsInstance(s, Split)
232 """Verify that Split methods returning gnc_numeric by value are wrapped."""
236 self.
book = self.
ses.get_book()
237 table = self.
book.get_table()
238 currency = table.lookup(
"CURRENCY",
"USD")
240 root = self.
book.get_root_account()
241 acct = Account(self.
book)
242 acct.SetCommodity(currency)
243 root.append_child(acct)
245 other = Account(self.
book)
246 other.SetCommodity(currency)
247 root.append_child(other)
249 tx = Transaction(self.
book)
251 tx.SetCurrency(currency)
252 tx.SetDateEnteredSecs(datetime.now())
253 tx.SetDatePostedSecs(datetime.now())
256 self.
split.SetParent(tx)
257 self.
split.SetAccount(acct)
258 self.
split.SetAmount(GncNumeric(100, 1))
259 self.
split.SetValue(GncNumeric(100, 1))
261 s2 = Split(self.
book)
264 s2.SetAmount(GncNumeric(-100, 1))
265 s2.SetValue(GncNumeric(-100, 1))
272 def test_get_noclosing_balance_returns_gnc_numeric(self):
273 val = self.
split.GetNoclosingBalance()
274 self.assertIsInstance(val, GncNumeric)
276 def test_get_cap_gains_returns_gnc_numeric(self):
277 val = self.
split.GetCapGains()
278 self.assertIsInstance(val, GncNumeric)
390 """Verify that get_latest_price, get_nearest_price, and
391 get_nearest_before_price return GncNumeric instead of raw
394 def test_get_latest_price_returns_gnc_numeric(self):
396 self.assertIsInstance(val, GncNumeric)
397 self.assertNotEqual(float(val), 0.0,
398 "Expected a non-zero price for TSTK/USD")
400 def test_get_nearest_price_returns_gnc_numeric(self):
401 date = datetime(2025, 1, 20)
403 self.assertIsInstance(val, GncNumeric)
405 def test_get_nearest_before_price_returns_gnc_numeric(self):
406 date = datetime(2025, 7, 1)
407 val = self.
pricedb.get_nearest_before_price(
409 self.assertIsInstance(val, GncNumeric)
412 """Verify the returned GncNumeric supports arithmetic."""
415 self.assertIsInstance(doubled, GncNumeric)
416 self.assertAlmostEqual(float(doubled), float(val) * 2, places=6)
423 """Verify that passing a wrapper object as instance= to a wrapper
424 class constructor unwraps it instead of creating a broken object."""
426 def test_gnc_numeric_double_wrap(self):
427 original = GncNumeric(7, 3)
428 double = GncNumeric(instance=original)
429 self.assertEqual(double.num(), 7)
430 self.assertEqual(double.denom(), 3)
432 def test_gnc_numeric_double_wrap_arithmetic(self):
433 original = GncNumeric(1, 4)
434 double = GncNumeric(instance=original)
435 result = double + GncNumeric(3, 4)
436 self.assertAlmostEqual(float(result), 1.0, places=6)
438 def test_gnc_commodity_double_wrap(self):
440 book = ses.get_book()
441 table = book.get_table()
442 usd = table.lookup(
"CURRENCY",
"USD")
443 double = GncCommodity(instance=usd)
444 self.assertIsInstance(double, GncCommodity)
445 self.assertEqual(double.get_mnemonic(),
"USD")
449 """Passing a raw SWIG proxy as instance= must still work."""
450 from gnucash
import gnucash_core_c
as gc
451 raw = gc.gnc_numeric_create(5, 2)
452 val = GncNumeric(instance=raw)
453 self.assertEqual(val.num(), 5)
454 self.assertEqual(val.denom(), 2)
461 """PriceSource must cover the C PRICE_SOURCE_* enum exactly, so it can't
462 drift if a value is added, removed, or renumbered on the C side. Member
463 names are a Python-side choice (e.g. FINANCE_QUOTE spells out FQ), so the
464 check is on the set of values, which come straight from the C enum."""
466 def test_values_match_c_enum(self):
467 from gnucash
import gnucash_core_c
as c
468 c_values = sorted(getattr(c, n)
for n
in dir(c)
469 if n.startswith(
"PRICE_SOURCE_"))
470 py_values = sorted(m.value
for m
in PriceSource)
471 self.assertEqual(py_values, c_values,
472 "PriceSource is out of sync with the C PRICE_SOURCE_* "
473 "enum -- add/remove members to match.")
475 def test_set_source_accepts_enum(self):
477 book = ses.get_book()
478 table = book.get_table()
479 usd = table.lookup(
"CURRENCY",
"USD")
480 stock = GncCommodity(book,
"Test Stock",
"NASDAQ",
"TSTK",
"TSTK", 10000)
482 price = GncPrice(book)
483 price.set_commodity(stock)
484 price.set_currency(usd)
485 price.set_source(PriceSource.FINANCE_QUOTE)
486 self.assertEqual(price.get_source(), PriceSource.FINANCE_QUOTE)
487 self.assertEqual(price.get_source_string(),
"Finance::Quote")
496 """set_source_string() is deprecated in favour of set_source(PriceSource.*),
497 but while it exists it must fail loud on an unrecognized source string
498 instead of silently leaving the source unchanged (the C setter no-ops).
499 These tests ignore the DeprecationWarning and check the guard behaviour;
500 the warning itself is covered by test_emits_deprecation_warning."""
505 self.
_warn_ctx = warnings.catch_warnings()
507 warnings.simplefilter(
"ignore", DeprecationWarning)
510 self.
_warn_ctx.__exit__(
None,
None,
None)
513 def test_canonical_source_is_accepted(self):
514 self.
price1.set_source_string(
"Finance::Quote")
515 self.assertEqual(self.
price1.get_source_string(),
"Finance::Quote")
517 def test_unknown_source_on_fresh_price_raises(self):
518 price = GncPrice(self.
book)
519 with self.assertRaises(ValueError):
520 price.set_source_string(
"not-a-real-source")
522 def test_unknown_source_does_not_clobber_existing(self):
526 self.
price1.set_source_string(
"Finance::Quote")
527 with self.assertRaises(ValueError):
528 self.
price1.set_source_string(
"totally-bogus")
529 self.assertEqual(self.
price1.get_source_string(),
"Finance::Quote")
531 def test_emits_deprecation_warning(self):
532 with warnings.catch_warnings():
533 warnings.simplefilter(
"error", DeprecationWarning)
534 with self.assertRaises(DeprecationWarning):
535 self.
price1.set_source_string(
"Finance::Quote")