Skip to content

Commit 29e2060

Browse files
Coding-Dev-ToolsDevForge Engineer
andauthored
test(renderer): add 14 tests for diff_renderer covering sensitive values, destructive warnings, empty plans, all providers, grouping, and edge cases (#11)
Co-authored-by: DevForge Engineer <engineer@devforge.dev>
1 parent c368793 commit 29e2060

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

tests/test_deploydiff.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,199 @@ def test_render_basic_plan(self, sample_terraform_plan):
421421
assert "DeployDiff" in output
422422
assert "Change Summary" in output
423423

424+
def test_render_empty_plan(self):
425+
"""Render an empty plan shows no changes."""
426+
from io import StringIO
427+
from rich.console import Console
428+
plan = DeployPlan(source=ChangeSource.TERRAFORM, changes=[])
429+
buf = StringIO()
430+
console = Console(file=buf, force_terminal=True)
431+
render_plan(plan, console)
432+
output = buf.getvalue()
433+
assert "DeployDiff" in output
434+
assert "0 resource(s)" not in output
435+
436+
def test_render_verbose_terraform(self, sample_terraform_plan):
437+
"""Verbose mode shows before/after details for each change."""
438+
from io import StringIO
439+
from rich.console import Console
440+
plan = parse_terraform_plan(sample_terraform_plan)
441+
buf = StringIO()
442+
console = Console(file=buf, force_terminal=True)
443+
render_plan(plan, console, verbose=True)
444+
output = buf.getvalue()
445+
assert "instance_type" in output
446+
assert "t3.micro" in output
447+
448+
def test_render_verbose_with_sensitive(self):
449+
"""Verbose mode masks sensitive values."""
450+
from io import StringIO
451+
from rich.console import Console
452+
change = ResourceChange(
453+
address="aws_db_instance.db",
454+
action=ChangeAction.UPDATE,
455+
resource_type="aws_db_instance",
456+
resource_name="db",
457+
source=ChangeSource.TERRAFORM,
458+
before={"password": "secret123", "port": 5432},
459+
after={"password": "newsecret", "port": 5432},
460+
before_sensitive={"password"},
461+
after_sensitive={"password"},
462+
)
463+
plan = DeployPlan(source=ChangeSource.TERRAFORM, changes=[change])
464+
buf = StringIO()
465+
console = Console(file=buf, force_terminal=True)
466+
render_plan(plan, console, verbose=True)
467+
output = buf.getvalue()
468+
# Check for "sensitive value" text (may be split by ANSI codes around parentheses)
469+
assert "sensitive value" in output
470+
assert "secret123" not in output
471+
assert "5432" in output
472+
473+
def test_render_destructive_change_warning(self, sample_terraform_plan):
474+
"""Destructive changes trigger a warning message."""
475+
from io import StringIO
476+
from rich.console import Console
477+
plan = parse_terraform_plan(sample_terraform_plan)
478+
buf = StringIO()
479+
console = Console(file=buf, force_terminal=True)
480+
render_plan(plan, console)
481+
output = buf.getvalue()
482+
# "destructive" appears contiguously even with ANSI codes
483+
assert "destructive" in output.lower()
484+
485+
def test_render_plan_without_destructive_changes(self):
486+
"""Plan with only creates/updates should not show destructive warning."""
487+
from io import StringIO
488+
from rich.console import Console
489+
changes = [
490+
ResourceChange(
491+
address="aws_instance.web",
492+
action=ChangeAction.CREATE,
493+
resource_type="aws_instance",
494+
resource_name="web",
495+
source=ChangeSource.TERRAFORM,
496+
),
497+
ResourceChange(
498+
address="aws_db_instance.db",
499+
action=ChangeAction.UPDATE,
500+
resource_type="aws_db_instance",
501+
resource_name="db",
502+
source=ChangeSource.TERRAFORM,
503+
),
504+
]
505+
plan = DeployPlan(source=ChangeSource.TERRAFORM, changes=changes)
506+
buf = StringIO()
507+
console = Console(file=buf, force_terminal=True)
508+
render_plan(plan, console)
509+
output = buf.getvalue()
510+
assert "destructive" not in output.lower()
511+
512+
def test_render_cfn_plan(self, sample_cfn_changeset):
513+
"""Render a CloudFormation plan."""
514+
from io import StringIO
515+
from rich.console import Console
516+
plan = parse_cloudformation_changeset(sample_cfn_changeset)
517+
buf = StringIO()
518+
console = Console(file=buf, force_terminal=True)
519+
render_plan(plan, console)
520+
output = buf.getvalue()
521+
assert "Cloudformation" in output or "CloudFormation" in output
522+
assert "Change Summary" in output
523+
524+
def test_render_pulumi_plan(self, sample_pulumi_preview):
525+
"""Render a Pulumi plan."""
526+
from io import StringIO
527+
from rich.console import Console
528+
plan = parse_pulumi_preview(sample_pulumi_preview)
529+
buf = StringIO()
530+
console = Console(file=buf, force_terminal=True)
531+
render_plan(plan, console)
532+
output = buf.getvalue()
533+
assert "Pulumi" in output
534+
535+
def test_render_replacement(self):
536+
"""Render a plan with a replacement change."""
537+
from io import StringIO
538+
from rich.console import Console
539+
change = ResourceChange(
540+
address="module.vpc.aws_nat_gateway.main",
541+
action=ChangeAction.REPLACE,
542+
resource_type="aws_nat_gateway",
543+
resource_name="main",
544+
source=ChangeSource.TERRAFORM,
545+
before={"connectivity_type": "public"},
546+
after={"connectivity_type": "private"},
547+
module_path="module.vpc",
548+
)
549+
plan = DeployPlan(source=ChangeSource.TERRAFORM, changes=[change])
550+
buf = StringIO()
551+
console = Console(file=buf, force_terminal=True)
552+
render_plan(plan, console)
553+
output = buf.getvalue()
554+
assert "⇄" in output or "will be replaced" in output.lower()
555+
556+
def test_render_change_details_missing_data(self):
557+
"""Render change details with no before/after should not error."""
558+
from io import StringIO
559+
from rich.console import Console
560+
from deploydiff.diff_renderer import _render_change_details
561+
change = ResourceChange(
562+
address="aws_instance.web",
563+
action=ChangeAction.CREATE,
564+
resource_type="aws_instance",
565+
resource_name="web",
566+
source=ChangeSource.TERRAFORM,
567+
before=None,
568+
after=None,
569+
)
570+
buf = StringIO()
571+
console = Console(file=buf, force_terminal=True)
572+
# Should not raise
573+
_render_change_details(change, console)
574+
output = buf.getvalue()
575+
assert output == ""
576+
577+
def test_group_by_action(self):
578+
"""Grouping changes by action produces correct buckets."""
579+
from deploydiff.diff_renderer import _group_by_action
580+
changes = [
581+
ResourceChange("a", ChangeAction.CREATE, "t", "n", ChangeSource.TERRAFORM),
582+
ResourceChange("b", ChangeAction.CREATE, "t", "n", ChangeSource.TERRAFORM),
583+
ResourceChange("c", ChangeAction.UPDATE, "t", "n", ChangeSource.TERRAFORM),
584+
ResourceChange("d", ChangeAction.DELETE, "t", "n", ChangeSource.TERRAFORM),
585+
]
586+
plan = DeployPlan(source=ChangeSource.TERRAFORM, changes=changes)
587+
groups = _group_by_action(plan)
588+
assert len(groups[ChangeAction.CREATE]) == 2
589+
assert len(groups[ChangeAction.UPDATE]) == 1
590+
assert len(groups[ChangeAction.DELETE]) == 1
591+
assert ChangeAction.CREATE_BEFORE_DELETE not in groups
592+
593+
def test_render_console_none(self):
594+
"""Renderer creates its own Console if none is provided."""
595+
plan = DeployPlan(source=ChangeSource.TERRAFORM, changes=[])
596+
# Should not raise when console is None
597+
render_plan(plan)
598+
599+
def test_render_create_before_delete_action_label(self):
600+
"""Create-before-delete action has the right label."""
601+
from deploydiff.diff_renderer import ACTION_LABELS
602+
label = ACTION_LABELS[ChangeAction.CREATE_BEFORE_DELETE]
603+
assert "create-first" in label
604+
605+
def test_render_no_op_label(self):
606+
"""No-op action has the right label."""
607+
from deploydiff.diff_renderer import ACTION_LABELS
608+
label = ACTION_LABELS[ChangeAction.NO_OP]
609+
assert label == "no changes"
610+
611+
def test_render_import_action_label(self):
612+
"""Import action has the right label."""
613+
from deploydiff.diff_renderer import ACTION_LABELS
614+
label = ACTION_LABELS[ChangeAction.IMPORT]
615+
assert "imported" in label
616+
424617

425618
# ── CLI Integration Tests ─────────────────────────────────────────────────
426619

0 commit comments

Comments
 (0)