1import {
2 formatDeclaredPropertyYaml,
3 formatDriftPropertyYaml,
4 formatYamlLines,
5} from "@/Cli/PropertyDiff.ts";
6import * as Output from "@/Output.ts";
7import { describe, expect, test } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import * as Redacted from "effect/Redacted";
10
11describe("YAML property display", { tags: ["unit", "local"] }, () => {
12 test("preserves nested create properties", () => {
13 expect(
14 formatDeclaredPropertyYaml(
15 {},
16 { config: { ports: [80, 443], region: "iad" } },
17 "create",
18 )?.lines,
19 ).toEqual([
20 "properties:",
21 " config:",
22 " ports:",
23 " - 80",
24 " - 443",
25 " region: iad",
26 ]);
27 });
28
29 test("shows contextual git-style YAML for updates", () => {
30 expect(
31 formatDeclaredPropertyYaml(
32 { id: "same", config: { enabled: true, retries: 2 } },
33 { id: "same", config: { enabled: true, retries: 3 } },
34 "update",
35 )?.lines,
36 ).toEqual([
37 "properties:",
38 " config:",
39 " enabled: true",
40 "- retries: 2",
41 "+ retries: 3",
42 " id: same",
43 ]);
44 });
45
46 test("does not invent property changes", () => {
47 expect(
48 formatDeclaredPropertyYaml({ name: "same" }, { name: "same" }, "replace"),
49 ).toBeUndefined();
50 });
51
52 test("shows only changed drift attributes", () => {
53 expect(
54 formatDriftPropertyYaml(
55 { id: "same", config: { enabled: true, retries: 2 } },
56 { id: "same", config: { enabled: true, retries: 5 } },
57 ).lines,
58 ).toEqual([
59 "config:",
60 " enabled: true",
61 "- retries: 2",
62 "+ retries: 5",
63 "id: same",
64 ]);
65 });
66
67 test("identifies resources missing from the cloud", () => {
68 expect(
69 formatDriftPropertyYaml({ id: "expected" }, undefined, true).lines,
70 ).toEqual(["- id: expected", "+ (missing)"]);
71 });
72
73 test("never evaluates or reveals deferred and secret values", () => {
74 let evaluated = false;
75 const lines = formatYamlLines({
76 output: Output.fromEffect(
77 Effect.sync(() => {
78 evaluated = true;
79 return "resolved";
80 }),
81 ),
82 secret: Redacted.make("super-secret"),
83 task: Effect.sync(() => "computed"),
84 }).join("\n");
85 expect(evaluated).toBe(false);
86 expect(lines).toContain("output: (known after apply)");
87 expect(lines).toContain("secret: (redacted)");
88 expect(lines).toContain("task: (computed)");
89 expect(lines).not.toContain("super-secret");
90 expect(lines).not.toContain("resolved");
91 });
92
93 test("shows a secret change without exposing either value", () => {
94 const lines = formatDeclaredPropertyYaml(
95 { token: Redacted.make("old-secret") },
96 { token: Redacted.make("new-secret") },
97 "update",
98 )?.lines.join("\n");
99 expect(lines).toContain("properties:\n token: (redacted)");
100 expect(lines).not.toContain("old-secret");
101 expect(lines).not.toContain("new-secret");
102 });
103
104 test("handles bigint and cycles", () => {
105 const value: Record<string, unknown> = { size: 2n };
106 value.self = value;
107 expect(formatYamlLines(value)).toEqual(["self: (circular)", "size: 2n"]);
108 });
109});