Skip to content

Commit 4f46085

Browse files
author
DevForge Engineer
committed
cowork-bot: fix DELETE_BEFORE_CREATE cost estimation + deepcopy pricing defaults
- Remove DELETE_BEFORE_CREATE from the zero-after-cost guard so replacements report the new resource cost instead of $0. - Use copy.deepcopy for DEFAULT_PRICICING to prevent global state mutation from custom pricing loads leaking across calls/tests. - Add regression test for delete-before-create after cost.
1 parent 529a42d commit 4f46085

2 files changed

Lines changed: 23 additions & 6 deletions

File tree

src/deploydiff/cost_estimator.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import copy
56
import json
67
from pathlib import Path
78

@@ -199,10 +200,7 @@ def _estimate_resource_cost(
199200
# If deleting, after cost is 0; if creating, before cost is 0
200201
if before and change.action == ChangeAction.CREATE:
201202
return 0.0
202-
if not before and change.action in (
203-
ChangeAction.DELETE,
204-
ChangeAction.DELETE_BEFORE_CREATE,
205-
):
203+
if not before and change.action == ChangeAction.DELETE:
206204
return 0.0
207205

208206
resource_type = change.resource_type
@@ -251,7 +249,7 @@ def _load_pricing(
251249
custom = json.load(f)
252250

253251
# Merge with defaults (custom overrides)
254-
merged = DEFAULT_PRICING.copy()
252+
merged = copy.deepcopy(DEFAULT_PRICING)
255253
for resource_type, prices in custom.items():
256254
if resource_type in merged:
257255
merged[resource_type].update(prices)

tests/test_edge_cases.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from rich.console import Console
1717

1818
from deploydiff.cli import _load_plan, _render_costs
19-
from deploydiff.cost_estimator import DEFAULT_PRICING, _load_pricing
19+
from deploydiff.cost_estimator import DEFAULT_PRICING, _load_pricing, estimate_costs
2020
from deploydiff.models import (
2121
ChangeAction,
2222
ChangeSource,
@@ -94,6 +94,25 @@ def test_load_pricing_custom_type_not_in_defaults(self, tmp_path):
9494
# Defaults should still be present
9595
assert "t3.micro" in pricing["aws_instance"]
9696

97+
def test_delete_before_create_has_nonzero_after_cost(self):
98+
"""DELETE_BEFORE_CREATE should report the new resource cost as after_cost."""
99+
pricing = _load_pricing()
100+
change = ResourceChange(
101+
address="aws_instance.replaced",
102+
action=ChangeAction.DELETE_BEFORE_CREATE,
103+
resource_type="aws_instance",
104+
resource_name="replaced",
105+
source=ChangeSource.TERRAFORM,
106+
before={"instance_type": "t3.micro"},
107+
after={"instance_type": "t3.large"},
108+
)
109+
plan = DeployPlan(source=ChangeSource.TERRAFORM, changes=[change])
110+
estimates = estimate_costs(plan)
111+
assert len(estimates) == 1
112+
est = estimates[0]
113+
assert est.monthly_cost_before == pricing["aws_instance"]["t3.micro"]
114+
assert est.monthly_cost_after == pricing["aws_instance"]["t3.large"]
115+
97116

98117
class TestRollbackEdgeCases:
99118
"""Tests for uncovered rollback paths."""

0 commit comments

Comments
 (0)