1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as dns from "@distilled.cloud/cloudflare/dns";
6import { describe, expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10const { test } = Test.make({ providers: Cloudflare.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17// Ride out fresh-token 403 blips on out-of-band calls.
18const retryForbidden = <A, E extends { _tag: string }, R>(
19 eff: Effect.Effect<A, E, R>,
20) =>
21 eff.pipe(
22 Effect.retry({
23 while: (e) => e._tag === "Forbidden",
24 schedule: Schedule.exponential("500 millis"),
25 times: 8,
26 }),
27 );
28
29const getSettings = (accountId: string) =>
30 retryForbidden(dns.getSettingAccount({ accountId }));
31
32// Baselines for the (entitlement-free) zone-default fields these tests
33// manage. NOTE: `zoneDefaults.nsTtl`, custom SOA values, and custom zone
34// modes are entitlement-gated on the testing account (code 1003 — the
35// typed `DnsSettingNotAvailable`), and `zoneDefaults.flattenAllCnames`
36// is rejected outright ("Setting an account-level default for CNAME
37// flattening is not supported"), so the tests exercise `multiProvider`
38// and `secondaryOverrides` instead. These only affect defaults for
39// FUTURE zones, so mutating them is safe on a shared test account.
40const BASELINE_MULTI_PROVIDER = false;
41const BASELINE_SECONDARY_OVERRIDES = false;
42
43// Normalize the singleton to a known baseline so each run starts from
44// the same cloud state regardless of what a previous (possibly
45// interrupted) run left behind.
46const normalizeBaseline = (accountId: string) =>
47 Effect.gen(function* () {
48 const observed = yield* getSettings(accountId);
49 if (
50 observed.zoneDefaults.multiProvider === BASELINE_MULTI_PROVIDER &&
51 observed.zoneDefaults.secondaryOverrides === BASELINE_SECONDARY_OVERRIDES
52 ) {
53 return;
54 }
55 yield* retryForbidden(
56 dns.patchSettingAccount({
57 accountId,
58 zoneDefaults: {
59 multiProvider: BASELINE_MULTI_PROVIDER,
60 secondaryOverrides: BASELINE_SECONDARY_OVERRIDES,
61 },
62 }),
63 );
64 });
65
66describe.sequential(
67 "AccountSettings",
68 { tags: ["provider:cloudflare", "provider:cloudflare:dns", "live"] },
69 () => {
70 test.provider(
71 "list returns the account's DNS settings singleton",
72 (stack) =>
73 Effect.gen(function* () {
74 const { accountId } = yield* yield* CloudflareEnvironment;
75
76 // Account singleton — read-only enumeration, no mutation.
77 yield* stack.destroy();
78
79 const provider = yield* Provider.findProvider(
80 Cloudflare.DNS.AccountDnsSettings,
81 );
82 const all = yield* provider.list();
83
84 // Exactly the one account-wide settings object, fully typed.
85 expect(all.length).toEqual(1);
86 const [settings] = all;
87 expect(settings.accountId).toEqual(accountId);
88 expect(typeof settings.enforceDnsOnly).toEqual("boolean");
89 expect(typeof settings.zoneDefaults.multiProvider).toEqual("boolean");
90 // `read` mirror: nothing managed yet, snapshot is its own baseline.
91 expect(settings.managedKeys).toEqual([]);
92 expect(settings.initialSettings.zoneDefaults.multiProvider).toEqual(
93 settings.zoneDefaults.multiProvider,
94 );
95
96 yield* stack.destroy();
97 }).pipe(logLevel),
98 { timeout: 120_000 },
99 );
100
101 test.provider(
102 "pins a zone default and restores the pre-management value on destroy",
103 (stack) =>
104 Effect.gen(function* () {
105 const { accountId } = yield* yield* CloudflareEnvironment;
106
107 yield* stack.destroy();
108 yield* normalizeBaseline(accountId);
109
110 const settings = yield* stack.deploy(
111 Cloudflare.DNS.AccountDnsSettings("AccountDns", {
112 zoneDefaults: { multiProvider: true },
113 }),
114 );
115
116 expect(settings.accountId).toEqual(accountId);
117 expect(settings.zoneDefaults.multiProvider).toEqual(true);
118 // The pre-management snapshot was captured for restore-on-destroy.
119 expect(settings.initialSettings.zoneDefaults.multiProvider).toEqual(
120 BASELINE_MULTI_PROVIDER,
121 );
122 expect(settings.managedKeys).toContain("zoneDefaults.multiProvider");
123
124 // Out-of-band verify via the SDK.
125 const live = yield* getSettings(accountId);
126 expect(live.zoneDefaults.multiProvider).toEqual(true);
127
128 yield* stack.destroy();
129
130 // Destroy restored the managed field to its pre-management value.
131 const restored = yield* getSettings(accountId);
132 expect(restored.zoneDefaults.multiProvider).toEqual(
133 BASELINE_MULTI_PROVIDER,
134 );
135
136 // Re-running destroy is idempotent (nothing left to restore).
137 yield* stack.destroy();
138 const still = yield* getSettings(accountId);
139 expect(still.zoneDefaults.multiProvider).toEqual(
140 BASELINE_MULTI_PROVIDER,
141 );
142 }).pipe(logLevel),
143 { timeout: 300_000 },
144 );
145
146 test.provider(
147 "updates in place, unions managedKeys, restores all managed fields",
148 (stack) =>
149 Effect.gen(function* () {
150 const { accountId } = yield* yield* CloudflareEnvironment;
151
152 yield* stack.destroy();
153 yield* normalizeBaseline(accountId);
154
155 const initial = yield* stack.deploy(
156 Cloudflare.DNS.AccountDnsSettings("AccountDns", {
157 zoneDefaults: { multiProvider: true },
158 }),
159 );
160 expect(initial.zoneDefaults.multiProvider).toEqual(true);
161 expect(initial.managedKeys).toContain("zoneDefaults.multiProvider");
162
163 // Same singleton patched in place — a second managed field joins;
164 // the original snapshot survives the update.
165 const updated = yield* stack.deploy(
166 Cloudflare.DNS.AccountDnsSettings("AccountDns", {
167 zoneDefaults: { multiProvider: true, secondaryOverrides: true },
168 }),
169 );
170 expect(updated.zoneDefaults.multiProvider).toEqual(true);
171 expect(updated.zoneDefaults.secondaryOverrides).toEqual(true);
172 expect(updated.initialSettings.zoneDefaults.multiProvider).toEqual(
173 BASELINE_MULTI_PROVIDER,
174 );
175 expect(
176 updated.initialSettings.zoneDefaults.secondaryOverrides,
177 ).toEqual(BASELINE_SECONDARY_OVERRIDES);
178 expect(updated.managedKeys).toContain("zoneDefaults.multiProvider");
179 expect(updated.managedKeys).toContain(
180 "zoneDefaults.secondaryOverrides",
181 );
182
183 const live = yield* getSettings(accountId);
184 expect(live.zoneDefaults.multiProvider).toEqual(true);
185 expect(live.zoneDefaults.secondaryOverrides).toEqual(true);
186
187 // Drop `multiProvider` from props — the key stays managed (union
188 // across all reconciles) so destroy still restores it.
189 const dropped = yield* stack.deploy(
190 Cloudflare.DNS.AccountDnsSettings("AccountDns", {
191 zoneDefaults: { secondaryOverrides: true },
192 }),
193 );
194 expect(dropped.managedKeys).toContain("zoneDefaults.multiProvider");
195 expect(dropped.managedKeys).toContain(
196 "zoneDefaults.secondaryOverrides",
197 );
198
199 yield* stack.destroy();
200
201 // Both managed fields were restored to their pre-management values.
202 const restored = yield* getSettings(accountId);
203 expect(restored.zoneDefaults.multiProvider).toEqual(
204 BASELINE_MULTI_PROVIDER,
205 );
206 expect(restored.zoneDefaults.secondaryOverrides).toEqual(
207 BASELINE_SECONDARY_OVERRIDES,
208 );
209 }).pipe(logLevel),
210 { timeout: 300_000 },
211 );
212 },
213);