102 lines
4.4 KiB
Python
102 lines
4.4 KiB
Python
"""Property-based invariant tests for the asset engine.
|
|
|
|
Hypothesis generates random inputs; we assert mathematical invariants
|
|
that must hold regardless of input."""
|
|
|
|
from hypothesis import given, settings, strategies as st, HealthCheck
|
|
from odoo.tests.common import TransactionCase
|
|
from odoo.tests import tagged
|
|
|
|
from odoo.addons.fusion_accounting_assets.services.depreciation_methods import (
|
|
straight_line, declining_balance, units_of_production,
|
|
)
|
|
|
|
|
|
@tagged('post_install', '-at_install', 'property_based')
|
|
class TestDepreciationInvariants(TransactionCase):
|
|
|
|
@given(
|
|
cost=st.floats(min_value=100.0, max_value=1000000.0,
|
|
allow_nan=False, allow_infinity=False),
|
|
salvage_pct=st.floats(min_value=0.0, max_value=0.5,
|
|
allow_nan=False, allow_infinity=False),
|
|
n_periods=st.integers(min_value=1, max_value=40),
|
|
)
|
|
@settings(max_examples=80, deadline=2000,
|
|
suppress_health_check=[HealthCheck.function_scoped_fixture])
|
|
def test_straight_line_total_equals_cost_minus_salvage(self, cost, salvage_pct, n_periods):
|
|
cost = round(cost, 2)
|
|
salvage = round(cost * salvage_pct, 2)
|
|
steps = straight_line(cost=cost, salvage_value=salvage, n_periods=n_periods)
|
|
total = sum(s.period_amount for s in steps)
|
|
# Within 1c rounding tolerance
|
|
self.assertAlmostEqual(
|
|
total, cost - salvage, places=1,
|
|
msg=f"cost={cost}, salvage={salvage}, n={n_periods}, total={total:.2f}",
|
|
)
|
|
|
|
@given(
|
|
cost=st.floats(min_value=100.0, max_value=1000000.0,
|
|
allow_nan=False, allow_infinity=False),
|
|
salvage_pct=st.floats(min_value=0.0, max_value=0.5,
|
|
allow_nan=False, allow_infinity=False),
|
|
n_periods=st.integers(min_value=1, max_value=20),
|
|
)
|
|
@settings(max_examples=50, deadline=2000,
|
|
suppress_health_check=[HealthCheck.function_scoped_fixture])
|
|
def test_straight_line_book_value_decreasing(self, cost, salvage_pct, n_periods):
|
|
cost = round(cost, 2)
|
|
salvage = round(cost * salvage_pct, 2)
|
|
steps = straight_line(cost=cost, salvage_value=salvage, n_periods=n_periods)
|
|
for i in range(1, len(steps)):
|
|
self.assertLessEqual(
|
|
steps[i].book_value_at_end,
|
|
steps[i - 1].book_value_at_end + 0.01,
|
|
)
|
|
|
|
@given(
|
|
cost=st.floats(min_value=1000.0, max_value=100000.0,
|
|
allow_nan=False, allow_infinity=False),
|
|
salvage_pct=st.floats(min_value=0.0, max_value=0.3,
|
|
allow_nan=False, allow_infinity=False),
|
|
n_periods=st.integers(min_value=2, max_value=20),
|
|
rate=st.floats(min_value=0.05, max_value=0.5,
|
|
allow_nan=False, allow_infinity=False),
|
|
)
|
|
@settings(max_examples=50, deadline=3000,
|
|
suppress_health_check=[HealthCheck.function_scoped_fixture])
|
|
def test_declining_balance_never_below_salvage(self, cost, salvage_pct, n_periods, rate):
|
|
cost = round(cost, 2)
|
|
salvage = round(cost * salvage_pct, 2)
|
|
steps = declining_balance(
|
|
cost=cost, salvage_value=salvage,
|
|
n_periods=n_periods, rate=rate,
|
|
)
|
|
for s in steps:
|
|
self.assertGreaterEqual(
|
|
s.book_value_at_end, salvage - 0.01,
|
|
msg=f"cost={cost}, salvage={salvage}, rate={rate}, step={s}",
|
|
)
|
|
|
|
@given(
|
|
cost=st.floats(min_value=1000.0, max_value=100000.0,
|
|
allow_nan=False, allow_infinity=False),
|
|
total_units=st.floats(min_value=100.0, max_value=10000.0,
|
|
allow_nan=False, allow_infinity=False),
|
|
n_periods=st.integers(min_value=1, max_value=10),
|
|
)
|
|
@settings(max_examples=30, deadline=2000,
|
|
suppress_health_check=[HealthCheck.function_scoped_fixture])
|
|
def test_units_of_production_total_at_full_use_equals_depreciable(self, cost, total_units, n_periods):
|
|
cost = round(cost, 2)
|
|
salvage = 0.0
|
|
# Distribute total_units evenly across periods
|
|
per_period = total_units / n_periods
|
|
steps = units_of_production(
|
|
cost=cost, salvage_value=salvage,
|
|
total_units_expected=total_units,
|
|
units_per_period=[per_period] * n_periods,
|
|
)
|
|
total = sum(s.period_amount for s in steps)
|
|
self.assertAlmostEqual(total, cost - salvage, places=1)
|