"""Tests for the cost-estimate price book (deterministic math; no network).""" from woodshop import prices as P from woodshop.cutplan import ShopSettings, build_cut_plan from woodshop.scene import Scene def test_estimate_sums_sticks_and_applies_hst(): s = Scene() for _ in range(3): s.place("2x4", 40) # 3 × 40" -> 2 sticks plan = build_cut_plan(s) est = P.estimate(plan, {"2x4": 4.00}, hst=0.15) line = next(ln for ln in est.lines if ln.stock == "2x4") assert line.qty == 2 and line.unit_price == 4.00 and line.total == 8.00 assert est.subtotal == 8.00 assert est.tax == 1.20 assert est.total == 9.20 def test_plywood_priced_per_sheet(): s = Scene() s.place("ply-3/4", 40, width_in=20) s.place("ply-3/4", 40, width_in=20) # both fit one sheet plan = build_cut_plan(s) est = P.estimate(plan, {"ply-3/4": 63.98}, hst=0.0) line = next(ln for ln in est.lines if ln.stock == "ply-3/4") assert line.qty == 1 and line.unit_price == 63.98 and est.total == 63.98 def test_lumber_price_scales_with_stick_length(): s = Scene() s.place("2x4", 40) plan = build_cut_plan(s, settings=ShopSettings(stick_len_in=192)) # 16' sticks est = P.estimate(plan, {"2x4": 4.00}) # priced per 8' stick line = next(ln for ln in est.lines if ln.stock == "2x4") assert line.unit_price == 8.00 # 192/96 × $4 def test_missing_price_is_flagged_not_invented(): s = Scene() s.place("2x4", 40) plan = build_cut_plan(s) est = P.estimate(plan, {}) # empty book assert est.missing == ["2x4"] assert est.subtotal == 0.0 assert "No price on file" in P.format_estimate(est) def test_save_and_load_roundtrip(tmp_path, monkeypatch): monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)) P.save_prices({"2x4": 3.49, "ply-3/4": 59.99}) loaded = P.load_prices() assert loaded["2x4"] == 3.49 and loaded["ply-3/4"] == 59.99 # untouched stocks still come from defaults assert loaded["2x6"] == P.DEFAULT_PRICES["2x6"] def test_corrupt_price_file_falls_back_to_defaults(tmp_path, monkeypatch): monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)) path = P._config_path() path.parent.mkdir(parents=True, exist_ok=True) path.write_text("{ not json") assert P.load_prices()["2x4"] == P.DEFAULT_PRICES["2x4"] def test_format_estimate_empty(): assert "nothing to buy" in P.format_estimate(P.estimate(build_cut_plan(Scene()))) def test_parse_price_reads_json_ld(): html = ('') assert P._parse_price(html) == 3.98 def test_parse_price_none_when_absent(): assert P._parse_price("no price here") is None def test_material_multiplier_scales_price(): from woodshop.scene import Scene s = Scene() s.place("1x4", 24) s.set_material("p1", "oak") plan = build_cut_plan(s) est = P.estimate(plan, prices={"1x4": 5.0}, hst=0.0, multipliers={"oak": 3.0, "spruce": 1.0}) line = next(ln for ln in est.lines if ln.stock == "1x4") assert line.material == "oak" assert line.unit_price == 15.0 # 5 × 3.0 assert "oak" in line.label def test_default_species_priced_at_base(): from woodshop.scene import Scene s = Scene() s.place("2x4", 24) # default spruce, mult 1.0 plan = build_cut_plan(s) est = P.estimate(plan, prices={"2x4": 4.0}, hst=0.0) line = next(ln for ln in est.lines if ln.stock == "2x4") assert line.unit_price == 4.0 and line.label == "2x4" def test_mixed_species_same_stock_priced_separately(): from woodshop.scene import Scene s = Scene() s.place("1x4", 24) s.place("1x4", 24) s.set_material("p2", "walnut") plan = build_cut_plan(s) # groups (1x4,spruce) and (1x4,walnut) est = P.estimate(plan, prices={"1x4": 5.0}, hst=0.0, multipliers={"spruce": 1.0, "walnut": 5.0}) prices = {ln.material: ln.unit_price for ln in est.lines} assert prices["spruce"] == 5.0 and prices["walnut"] == 25.0 def test_multipliers_save_load(tmp_path, monkeypatch): monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)) P.save_material_multipliers({"oak": 3.5, "walnut": 6.0}) loaded = P.load_material_multipliers() assert loaded["oak"] == 3.5 and loaded["walnut"] == 6.0 assert loaded["spruce"] == P.DEFAULT_MATERIAL_MULTIPLIERS["spruce"]