1import * as AWS from "@/AWS";
2import { DBParameterGroup } from "@/AWS/RDS/DBParameterGroup.ts";
3import * as Drift from "@/Drift.ts";
4import * as Provider from "@/Provider";
5import * as Test from "@/Test/Alchemy";
6import * as rds from "@distilled.cloud/aws/rds";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import * as Schedule from "effect/Schedule";
10import * as Stream from "effect/Stream";
11
12const { test } = Test.make({ providers: AWS.providers() });
13
14/** The parameters RDS reports as user-set, which is what the resource owns. */
15const userParameters = Effect.fn(function* (name: string) {
16 const pages = yield* rds.describeDBParameters
17 .pages({ DBParameterGroupName: name, Source: "user" })
18 .pipe(Stream.runCollect);
19 return Object.fromEntries(
20 Array.from(pages)
21 .flatMap((page) => page.Parameters ?? [])
22 .flatMap((p) =>
23 p.ParameterName && p.ParameterValue !== undefined
24 ? [[p.ParameterName, p.ParameterValue] as const]
25 : [],
26 ),
27 );
28});
29
30const groupParameters = Effect.fn(function* (name: string) {
31 const pages = yield* rds.describeDBParameters
32 .pages({ DBParameterGroupName: name })
33 .pipe(Stream.runCollect);
34 return new Map(
35 Array.from(pages).flatMap((page) =>
36 (page.Parameters ?? []).flatMap((parameter) =>
37 parameter.ParameterName === undefined
38 ? []
39 : [[parameter.ParameterName, parameter] as const],
40 ),
41 ),
42 );
43});
44
45const waitForParameters = Effect.fn(function* (
46 name: string,
47 ready: (parameters: Map<string, rds.Parameter>) => boolean,
48) {
49 const parameters = yield* groupParameters(name).pipe(
50 Effect.repeat({
51 schedule: Schedule.spaced("3 seconds"),
52 times: 8,
53 until: ready,
54 }),
55 );
56 expect(ready(parameters)).toBe(true);
57 return parameters;
58});
59
60const modifyParameters = (name: string, parameters: rds.Parameter[]) =>
61 rds
62 .modifyDBParameterGroup({
63 DBParameterGroupName: name,
64 Parameters: parameters,
65 })
66 .pipe(
67 Effect.retry({
68 while: (error) => error._tag === "InvalidDBParameterGroupStateFault",
69 schedule: Schedule.spaced("3 seconds"),
70 times: 8,
71 }),
72 );
73
74const resetParameters = (name: string, parameters: rds.Parameter[]) =>
75 rds
76 .resetDBParameterGroup({
77 DBParameterGroupName: name,
78 ResetAllParameters: false,
79 Parameters: parameters,
80 })
81 .pipe(
82 Effect.retry({
83 while: (error) => error._tag === "InvalidDBParameterGroupStateFault",
84 schedule: Schedule.spaced("3 seconds"),
85 times: 8,
86 }),
87 );
88
89const assertGroupGone = Effect.fn(function* (name: string) {
90 const gone = yield* rds
91 .describeDBParameterGroups({
92 DBParameterGroupName: name,
93 })
94 .pipe(
95 Effect.as(false),
96 Effect.catchTag("DBParameterGroupNotFoundFault", () =>
97 Effect.succeed(true),
98 ),
99 Effect.repeat({
100 schedule: Schedule.spaced("3 seconds"),
101 times: 8,
102 until: (gone) => gone,
103 }),
104 );
105 expect(gone).toBe(true);
106});
107
108// Canonical `list()` test (AWS account/region-scoped collection). Parameter
109// groups create and delete fast (well within the 240s budget), so we deploy a
110// real group, resolve the provider via the typed `Provider.findProvider(
111// DBParameterGroup)` so `list()`'s element type is the exact
112// `DBParameterGroup["Attributes"]` shape, call it, and assert the deployed
113// group appears in the exhaustively-paginated result.
114test.provider(
115 "list enumerates the deployed DB parameter group",
116 (stack) =>
117 Effect.gen(function* () {
118 yield* stack.destroy();
119
120 const group = yield* stack.deploy(
121 Effect.gen(function* () {
122 return yield* DBParameterGroup("ListDBParameterGroup", {
123 dbParameterGroupName: "alchemy-test-dbpg-list",
124 family: "aurora-postgresql16",
125 description: "Alchemy list() test parameter group",
126 });
127 }),
128 );
129
130 const provider = yield* Provider.findProvider(DBParameterGroup);
131 const all = yield* provider.list();
132
133 expect(Array.isArray(all)).toBe(true);
134 expect(
135 all.some((g) => g.dbParameterGroupName === group.dbParameterGroupName),
136 ).toBe(true);
137
138 for (const g of all) {
139 expect(typeof g.dbParameterGroupName).toBe("string");
140 expect(typeof g.family).toBe("string");
141 }
142
143 yield* stack.destroy();
144 }),
145 { tags: ["provider:aws", "provider:aws:rds", "live"] },
146);
147
148// Parameters reconcile in place: a redeploy writes changed values and resets
149// keys the props dropped, both diffed against live `Source=user` state rather
150// than the prior props.
151test.provider(
152 "parameters are written, updated and reset",
153 (stack) =>
154 Effect.gen(function* () {
155 yield* stack.destroy();
156
157 const name = "alchemy-test-dbpg-params";
158 const deploy = (parameters: Record<string, string>) =>
159 stack.deploy(
160 Effect.gen(function* () {
161 return yield* DBParameterGroup("ParamsDBParameterGroup", {
162 dbParameterGroupName: name,
163 family: "mysql8.4",
164 description: "Alchemy parameters test parameter group",
165 parameters,
166 });
167 }),
168 );
169
170 const created = yield* deploy({
171 time_zone: "Australia/Sydney",
172 max_connections: "150",
173 });
174 expect(created.parameters.time_zone).toBe("Australia/Sydney");
175
176 const afterCreate = yield* userParameters(name);
177 expect(afterCreate.time_zone).toBe("Australia/Sydney");
178 expect(afterCreate.max_connections).toBe("150");
179
180 // time_zone changes; max_connections is dropped and must go back to the
181 // engine default, which removes it from Source=user entirely.
182 yield* deploy({ time_zone: "UTC" });
183
184 const afterUpdate = yield* userParameters(name);
185 expect(afterUpdate.time_zone).toBe("UTC");
186 expect(afterUpdate.max_connections).toBeUndefined();
187
188 yield* stack.destroy();
189 }),
190 { tags: ["provider:aws", "provider:aws:rds", "live"] },
191);
192
193test.provider(
194 "PR1589 adoption resets undeclared overrides to the same defaults as creation",
195 (stack) =>
196 Effect.gen(function* () {
197 yield* stack.destroy();
198 const name = "alchemy-test-pr1589-adopt-parameters";
199 yield* rds.createDBParameterGroup({
200 DBParameterGroupName: name,
201 DBParameterGroupFamily: "postgres16",
202 Description: "Desired-state adoption regression",
203 });
204 yield* modifyParameters(name, [
205 {
206 ParameterName: "work_mem",
207 ParameterValue: "8192",
208 ApplyMethod: "immediate",
209 },
210 ]);
211 yield* waitForParameters(
212 name,
213 (parameters) => parameters.get("work_mem")?.ParameterValue === "8192",
214 );
215 const program = DBParameterGroup("AdoptedParameters1589", {
216 dbParameterGroupName: name,
217 family: "postgres16",
218 description: "Desired-state adoption regression",
219 });
220 const adopted = yield* stack.deploy(program);
221 expect(adopted.parameters).toEqual({});
222 expect(yield* userParameters(name)).toEqual({});
223 expect(
224 (yield* stack.plan(program)).resources.AdoptedParameters1589?.action,
225 ).toBe("noop");
226 yield* stack.destroy();
227 yield* assertGroupGone(name);
228 }),
229 { tags: ["provider:aws", "provider:aws:rds", "live"], timeout: 120_000 },
230);
231
232test.provider(
233 "PR1589 resets omitted overrides and observes settled modify/reset outputs",
234 (stack) =>
235 Effect.gen(function* () {
236 yield* stack.destroy();
237 const program = (parameters?: Record<string, string>) =>
238 DBParameterGroup("ObservedParameters1589", {
239 family: "postgres16",
240 parameters,
241 });
242 const created = yield* stack.deploy(program());
243 const name = created.dbParameterGroupName;
244 const defaults = yield* groupParameters(name);
245 expect(defaults.get("max_connections")?.ApplyType).toBe("static");
246 expect(defaults.get("work_mem")?.ApplyType).toBe("dynamic");
247
248 yield* modifyParameters(name, [
249 {
250 ParameterName: "work_mem",
251 ParameterValue: "8192",
252 ApplyMethod: "immediate",
253 },
254 {
255 ParameterName: "max_connections",
256 ParameterValue: "200",
257 ApplyMethod: "pending-reboot",
258 },
259 ]);
260 yield* waitForParameters(
261 name,
262 (parameters) =>
263 parameters.get("work_mem")?.ParameterValue === "8192" &&
264 parameters.get("max_connections")?.ParameterValue === "200",
265 );
266 const overrides = { work_mem: "8192", max_connections: "200" };
267 expect(yield* userParameters(name)).toEqual(overrides);
268
269 const plan = yield* stack.plan(program());
270 expect(plan.resources.ObservedParameters1589?.action).toBe("update");
271 const observed = yield* stack.deploy(program());
272 expect(observed.parameters).toEqual({});
273 expect(yield* userParameters(name)).toEqual({});
274 const settled = yield* stack.plan(program());
275 expect(settled.resources.ObservedParameters1589?.action).toBe("noop");
276 const refreshed = yield* Drift.detect({
277 name: stack.name,
278 stage: stack.stage,
279 }).pipe(Effect.provide(stack.state));
280 expect(refreshed.resources.ObservedParameters1589).toMatchObject({
281 action: "unchanged",
282 attr: { parameters: {} },
283 });
284
285 const changed = yield* stack.deploy(
286 program({ work_mem: "16384", max_connections: "250" }),
287 );
288 expect(changed.dbParameterGroupName).toBe(name);
289 expect(changed.dbParameterGroupArn).toBe(created.dbParameterGroupArn);
290 expect(changed.parameters).toEqual({
291 work_mem: "16384",
292 max_connections: "250",
293 });
294 // No test-side wait: reconcile must return observed, settled group values.
295 expect(yield* userParameters(name)).toEqual(changed.parameters);
296 const resetStatic = yield* stack.deploy(program({ work_mem: "16384" }));
297 expect(resetStatic.parameters).toEqual({ work_mem: "16384" });
298 expect(yield* userParameters(name)).toEqual(resetStatic.parameters);
299 const afterReset = (yield* groupParameters(name)).get("max_connections");
300 expect(afterReset?.Source).toBe(defaults.get("max_connections")?.Source);
301 expect(afterReset?.ParameterValue).toBe(
302 defaults.get("max_connections")?.ParameterValue,
303 );
304
305 const cleared = yield* stack.deploy(program());
306 expect(cleared.parameters).toEqual({});
307 expect(yield* userParameters(name)).toEqual({});
308 expect(
309 (yield* groupParameters(name)).get("work_mem")?.ParameterValue,
310 ).toBe(defaults.get("work_mem")?.ParameterValue);
311 yield* stack.destroy();
312 yield* assertGroupGone(name);
313 }),
314 { tags: ["provider:aws", "provider:aws:rds", "live"], timeout: 120_000 },
315);
316
317test.provider(
318 "PR1589 refresh retains managed defaults after an out-of-band reset",
319 (stack) =>
320 Effect.gen(function* () {
321 yield* stack.destroy();
322 const program = (parameters?: Record<string, string>) =>
323 DBParameterGroup("DefaultParameters1589", {
324 family: "postgres16",
325 parameters,
326 });
327 const created = yield* stack.deploy(program());
328 const name = created.dbParameterGroupName;
329 const defaults = yield* groupParameters(name);
330 const parameter = defaults.get("log_autovacuum_min_duration");
331 expect(parameter?.Source).toBe("engine-default");
332 expect(parameter?.ApplyType).toBe("dynamic");
333 expect(parameter?.IsModifiable).toBe(true);
334 if (parameter?.ParameterValue === undefined) {
335 return yield* Effect.fail(
336 new Error(
337 "RDS did not report the log_autovacuum_min_duration default",
338 ),
339 );
340 }
341 const desired = { log_autovacuum_min_duration: parameter.ParameterValue };
342 const managed = yield* stack.deploy(program(desired));
343 expect(managed.parameters).toEqual(desired);
344 expect(
345 (yield* groupParameters(name)).get("log_autovacuum_min_duration")
346 ?.Source,
347 ).toBe("engine-default");
348 const initial = yield* Drift.detect({
349 name: stack.name,
350 stage: stack.stage,
351 }).pipe(Effect.provide(stack.state));
352 expect(initial.resources.DefaultParameters1589).toMatchObject({
353 action: "unchanged",
354 attr: { parameters: desired },
355 });
356
357 const driftedValue =
358 parameter.ParameterValue === "8192" ? "16384" : "8192";
359 yield* modifyParameters(name, [
360 {
361 ParameterName: "log_autovacuum_min_duration",
362 ParameterValue: driftedValue,
363 ApplyMethod: "immediate",
364 },
365 {
366 ParameterName: "max_connections",
367 ParameterValue: "200",
368 ApplyMethod: "pending-reboot",
369 },
370 ]);
371 yield* waitForParameters(
372 name,
373 (parameters) =>
374 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
375 driftedValue &&
376 parameters.get("max_connections")?.ParameterValue === "200",
377 );
378 const drift = yield* Drift.detect({
379 name: stack.name,
380 stage: stack.stage,
381 }).pipe(Effect.provide(stack.state));
382 expect(drift.resources.DefaultParameters1589?.attr.parameters).toEqual({
383 log_autovacuum_min_duration: driftedValue,
384 max_connections: "200",
385 });
386
387 yield* resetParameters(name, [
388 {
389 ParameterName: "log_autovacuum_min_duration",
390 ApplyMethod: "immediate",
391 },
392 ]);
393 yield* waitForParameters(
394 name,
395 (parameters) =>
396 parameters.get("log_autovacuum_min_duration")?.Source ===
397 "engine-default" &&
398 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
399 desired.log_autovacuum_min_duration,
400 );
401 const reset = yield* Drift.detect({
402 name: stack.name,
403 stage: stack.stage,
404 }).pipe(Effect.provide(stack.state));
405 expect(reset.resources.DefaultParameters1589?.attr.parameters).toEqual({
406 ...desired,
407 max_connections: "200",
408 });
409 expect(yield* userParameters(name)).toEqual({ max_connections: "200" });
410 yield* stack.destroy();
411 yield* assertGroupGone(name);
412 }),
413 { tags: ["provider:aws", "provider:aws:rds", "live"], timeout: 120_000 },
414);
415
416test.provider(
417 "PR1590 adoption resets undeclared overrides to the same defaults as creation",
418 (stack) =>
419 Effect.gen(function* () {
420 yield* stack.destroy();
421 const name = "alchemy-test-pr1590-adopt-parameters";
422 yield* rds.createDBParameterGroup({
423 DBParameterGroupName: name,
424 DBParameterGroupFamily: "postgres16",
425 Description: "Desired-state adoption regression",
426 });
427 yield* modifyParameters(name, [
428 {
429 ParameterName: "work_mem",
430 ParameterValue: "8192",
431 ApplyMethod: "immediate",
432 },
433 ]);
434 yield* waitForParameters(
435 name,
436 (parameters) => parameters.get("work_mem")?.ParameterValue === "8192",
437 );
438 const program = DBParameterGroup("AdoptedParameters1590", {
439 dbParameterGroupName: name,
440 family: "postgres16",
441 description: "Desired-state adoption regression",
442 });
443 const adopted = yield* stack.deploy(program);
444 expect(adopted.parameters).toEqual({});
445 expect(yield* userParameters(name)).toEqual({});
446 expect(
447 (yield* stack.plan(program)).resources.AdoptedParameters1590?.action,
448 ).toBe("noop");
449 yield* stack.destroy();
450 yield* assertGroupGone(name);
451 }),
452 { tags: ["provider:aws", "provider:aws:rds", "live"], timeout: 120_000 },
453);
454
455test.provider(
456 "PR1590 plans and repairs live modify/reset drift with unchanged props",
457 (stack) =>
458 Effect.gen(function* () {
459 yield* stack.destroy();
460 const program = (parameters?: Record<string, string>) =>
461 Effect.gen(function* () {
462 const group = yield* DBParameterGroup("DriftParameters1590", {
463 family: "postgres16",
464 parameters,
465 });
466 const dependent = yield* DBParameterGroup("StableReference1590", {
467 family: "postgres16",
468 tags: {
469 groupName: group.dbParameterGroupName,
470 groupArn: group.dbParameterGroupArn.as<string>(),
471 },
472 });
473 return { group, dependent };
474 });
475 const initial = yield* stack.deploy(program({}));
476 const name = initial.group.dbParameterGroupName;
477 const defaults = yield* groupParameters(name);
478 const parameter = defaults.get("log_autovacuum_min_duration");
479 expect(parameter?.Source).toBe("engine-default");
480 expect(parameter?.ApplyType).toBe("dynamic");
481 expect(parameter?.IsModifiable).toBe(true);
482 const defaultValue = parameter?.ParameterValue;
483 expect(defaultValue).toBeDefined();
484 const desired = {
485 log_autovacuum_min_duration: defaultValue === "8192" ? "16384" : "8192",
486 };
487 const created = yield* stack.deploy(program(desired));
488 yield* waitForParameters(
489 name,
490 (parameters) =>
491 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
492 desired.log_autovacuum_min_duration,
493 );
494 const clean = yield* stack.plan(program(desired));
495 expect(clean.resources.DriftParameters1590?.action).toBe("noop");
496 expect(clean.resources.StableReference1590?.action).toBe("noop");
497
498 const driftedValue =
499 desired.log_autovacuum_min_duration === "8192" ? "16384" : "8192";
500 yield* modifyParameters(name, [
501 {
502 ParameterName: "log_autovacuum_min_duration",
503 ParameterValue: driftedValue,
504 ApplyMethod: "immediate",
505 },
506 {
507 ParameterName: "max_connections",
508 ParameterValue: "200",
509 ApplyMethod: "pending-reboot",
510 },
511 ]);
512 yield* waitForParameters(
513 name,
514 (parameters) =>
515 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
516 driftedValue &&
517 parameters.get("max_connections")?.ParameterValue === "200",
518 );
519 // No refresh before planning: persisted props and outputs still match.
520 const drift = yield* stack.plan(program(desired));
521 expect(drift.resources.DriftParameters1590?.action).toBe("update");
522 expect(drift.resources.StableReference1590?.action).toBe("noop");
523 expect(yield* userParameters(name)).toEqual({
524 log_autovacuum_min_duration: driftedValue,
525 max_connections: "200",
526 });
527
528 const repaired = yield* stack.deploy(program(desired));
529 expect(repaired.group.dbParameterGroupName).toBe(name);
530 expect(repaired.group.dbParameterGroupArn).toBe(
531 created.group.dbParameterGroupArn,
532 );
533 expect(repaired.group.parameters).toEqual(desired);
534 expect(yield* userParameters(name)).toEqual(desired);
535 expect(
536 (yield* stack.plan(program(desired))).resources.DriftParameters1590
537 ?.action,
538 ).toBe("noop");
539
540 yield* resetParameters(name, [
541 {
542 ParameterName: "log_autovacuum_min_duration",
543 ApplyMethod: "immediate",
544 },
545 ]);
546 yield* waitForParameters(
547 name,
548 (parameters) =>
549 parameters.get("log_autovacuum_min_duration")?.Source ===
550 "engine-default" &&
551 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
552 defaultValue,
553 );
554 const resetDrift = yield* stack.plan(program(desired));
555 expect(resetDrift.resources.DriftParameters1590?.action).toBe("update");
556 expect(resetDrift.resources.StableReference1590?.action).toBe("noop");
557 expect(yield* userParameters(name)).toEqual({});
558 yield* stack.deploy(program(desired));
559 yield* waitForParameters(
560 name,
561 (parameters) =>
562 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
563 desired.log_autovacuum_min_duration,
564 );
565 expect(yield* userParameters(name)).toEqual(desired);
566
567 const omitted = yield* stack.deploy(program());
568 expect(omitted.group.parameters).toEqual({});
569 expect(yield* userParameters(name)).toEqual({});
570 yield* modifyParameters(name, [
571 {
572 ParameterName: "max_connections",
573 ParameterValue: "200",
574 ApplyMethod: "pending-reboot",
575 },
576 ]);
577 yield* waitForParameters(
578 name,
579 (parameters) =>
580 parameters.get("max_connections")?.ParameterValue === "200",
581 );
582 const omittedDrift = yield* stack.plan(program());
583 expect(omittedDrift.resources.DriftParameters1590?.action).toBe("update");
584 expect(omittedDrift.resources.StableReference1590?.action).toBe("noop");
585 const cleared = yield* stack.deploy(program());
586 expect(cleared.group.parameters).toEqual({});
587 expect(yield* userParameters(name)).toEqual({});
588 expect(
589 (yield* stack.plan(program())).resources.DriftParameters1590?.action,
590 ).toBe("noop");
591 yield* stack.destroy();
592 yield* assertGroupGone(name);
593 yield* assertGroupGone(created.dependent.dbParameterGroupName);
594 }),
595 { tags: ["provider:aws", "provider:aws:rds", "live"], timeout: 240_000 },
596);
597
598test.provider(
599 "PR1590 treats a reset to an explicitly managed default as no drift",
600 (stack) =>
601 Effect.gen(function* () {
602 yield* stack.destroy();
603 const program = (parameters?: Record<string, string>) =>
604 DBParameterGroup("DefaultParameters1590", {
605 family: "postgres16",
606 parameters,
607 });
608 const created = yield* stack.deploy(program());
609 const name = created.dbParameterGroupName;
610 const parameter = (yield* groupParameters(name)).get(
611 "log_autovacuum_min_duration",
612 );
613 expect(parameter?.Source).toBe("engine-default");
614 expect(parameter?.ApplyType).toBe("dynamic");
615 expect(parameter?.IsModifiable).toBe(true);
616 if (parameter?.ParameterValue === undefined) {
617 return yield* Effect.fail(
618 new Error(
619 "RDS did not report the log_autovacuum_min_duration default",
620 ),
621 );
622 }
623 const desired = { log_autovacuum_min_duration: parameter.ParameterValue };
624 yield* stack.deploy(program(desired));
625 expect(
626 (yield* groupParameters(name)).get("log_autovacuum_min_duration")
627 ?.Source,
628 ).toBe("engine-default");
629 expect(
630 (yield* stack.plan(program(desired))).resources.DefaultParameters1590
631 ?.action,
632 ).toBe("noop");
633 const initial = yield* Drift.detect({
634 name: stack.name,
635 stage: stack.stage,
636 }).pipe(Effect.provide(stack.state));
637 expect(initial.resources.DefaultParameters1590).toMatchObject({
638 action: "unchanged",
639 attr: { parameters: desired },
640 });
641
642 const driftedValue =
643 desired.log_autovacuum_min_duration === "8192" ? "16384" : "8192";
644 yield* modifyParameters(name, [
645 {
646 ParameterName: "log_autovacuum_min_duration",
647 ParameterValue: driftedValue,
648 ApplyMethod: "immediate",
649 },
650 ]);
651 yield* waitForParameters(
652 name,
653 (parameters) =>
654 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
655 driftedValue,
656 );
657 const drift = yield* stack.plan(program(desired));
658 expect(drift.resources.DefaultParameters1590?.action).toBe("update");
659 yield* stack.deploy(program(desired));
660 yield* waitForParameters(
661 name,
662 (parameters) =>
663 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
664 desired.log_autovacuum_min_duration,
665 );
666 // Writing the default value need not remove Source=user; reset does.
667 yield* resetParameters(name, [
668 {
669 ParameterName: "log_autovacuum_min_duration",
670 ApplyMethod: "immediate",
671 },
672 ]);
673 yield* waitForParameters(
674 name,
675 (parameters) =>
676 parameters.get("log_autovacuum_min_duration")?.Source ===
677 "engine-default" &&
678 parameters.get("log_autovacuum_min_duration")?.ParameterValue ===
679 desired.log_autovacuum_min_duration,
680 );
681 expect(yield* userParameters(name)).toEqual({});
682 expect(
683 (yield* stack.plan(program(desired))).resources.DefaultParameters1590
684 ?.action,
685 ).toBe("noop");
686 const reset = yield* Drift.detect({
687 name: stack.name,
688 stage: stack.stage,
689 }).pipe(Effect.provide(stack.state));
690 expect(reset.resources.DefaultParameters1590).toMatchObject({
691 action: "unchanged",
692 attr: { parameters: desired },
693 });
694 yield* stack.destroy();
695 yield* assertGroupGone(name);
696 }),
697 { tags: ["provider:aws", "provider:aws:rds", "live"], timeout: 120_000 },
698);
699
700test.provider(
701 "PR1590 clears stable references and recreates an out-of-band deleted group",
702 (stack) =>
703 Effect.gen(function* () {
704 yield* stack.destroy();
705 const program = Effect.gen(function* () {
706 const group = yield* DBParameterGroup("MissingParameters1590", {
707 family: "postgres16",
708 parameters: {},
709 });
710 const dependent = yield* DBParameterGroup("MissingReference1590", {
711 family: "postgres16",
712 tags: {
713 groupName: group.dbParameterGroupName,
714 groupArn: group.dbParameterGroupArn.as<string>(),
715 },
716 });
717 return { group, dependent };
718 });
719 const created = yield* stack.deploy(program);
720 const name = created.group.dbParameterGroupName;
721 const clean = yield* stack.plan(program);
722 expect(clean.resources.MissingParameters1590?.action).toBe("noop");
723 expect(clean.resources.MissingReference1590?.action).toBe("noop");
724
725 // Intentional drift, not cleanup: the engine must discover and recreate it.
726 yield* rds.deleteDBParameterGroup({ DBParameterGroupName: name }).pipe(
727 Effect.retry({
728 while: (error) => error._tag === "InvalidDBParameterGroupStateFault",
729 schedule: Schedule.spaced("3 seconds"),
730 times: 8,
731 }),
732 );
733 yield* assertGroupGone(name);
734 const missing = yield* stack.plan(program);
735 expect(missing.resources.MissingParameters1590?.action).toBe("update");
736 expect(missing.resources.MissingReference1590?.action).toBe("update");
737 yield* assertGroupGone(name);
738
739 const recreated = yield* stack.deploy(program);
740 expect(recreated.group.dbParameterGroupName).toBe(name);
741 expect(recreated.group.dbParameterGroupArn).toBe(
742 created.group.dbParameterGroupArn,
743 );
744 expect(recreated.dependent.dbParameterGroupName).toBe(
745 created.dependent.dbParameterGroupName,
746 );
747 const live = yield* rds.describeDBParameterGroups({
748 DBParameterGroupName: name,
749 });
750 expect(live.DBParameterGroups?.[0]?.DBParameterGroupArn).toBe(
751 created.group.dbParameterGroupArn,
752 );
753 expect(
754 (yield* stack.plan(program)).resources.MissingReference1590?.action,
755 ).toBe("noop");
756 yield* stack.destroy();
757 yield* assertGroupGone(name);
758 yield* assertGroupGone(created.dependent.dbParameterGroupName);
759 }),
760 { tags: ["provider:aws", "provider:aws:rds", "live"], timeout: 120_000 },
761);