1import * as AWS from "@/AWS";
2import { User } from "@/AWS/MemoryDB";
3import * as Test from "@/Test/Alchemy";
4import * as memorydb from "@distilled.cloud/aws/memorydb";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Redacted from "effect/Redacted";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12// A fixed test password (16-128 printable chars). Not a real secret.
13const TEST_PASSWORD = Redacted.make("AlchemyMemoryDbTestPass01");
14
15// MemoryDB user deletion takes ~2 minutes server-side. Deletion is verified
16// as INITIATED (status `deleting`, irreversible) or fully gone — waiting for
17// full disappearance would burn most of the test's timeout budget.
18const assertDeletingOrGone = (name: string) =>
19 Effect.gen(function* () {
20 const status = yield* memorydb.describeUsers({ UserName: name }).pipe(
21 Effect.map((r) => r.Users?.[0]?.Status ?? "gone"),
22 Effect.catchTag("UserNotFoundFault", () =>
23 Effect.succeed("gone" as const),
24 ),
25 );
26 if (status !== "gone" && status !== "deleting") {
27 return yield* Effect.fail(
28 new Error(`user '${name}' still exists (status: ${status})`),
29 );
30 }
31 }).pipe(
32 Effect.retry({
33 schedule: Schedule.max([
34 Schedule.fixed("2 seconds"),
35 Schedule.recurs(15),
36 ]),
37 }),
38 );
39
40test.provider(
41 "create, update access string, delete a MemoryDB user",
42 (stack) =>
43 Effect.gen(function* () {
44 yield* stack.destroy();
45
46 const { user } = yield* stack.deploy(
47 Effect.gen(function* () {
48 const user = yield* User("AppUser", {
49 authenticationMode: {
50 type: "password",
51 passwords: [TEST_PASSWORD],
52 },
53 accessString: "on ~* +@all",
54 tags: { fixture: "memorydb-user" },
55 });
56 return { user };
57 }),
58 );
59
60 expect(user.userName).toBeDefined();
61 expect(user.userArn).toContain(":user/");
62 expect(user.accessString).toContain("~*");
63 expect(user.authenticationType).toBe("password");
64
65 // Out-of-band verification.
66 const described = yield* memorydb.describeUsers({
67 UserName: user.userName,
68 });
69 const observed = described.Users?.[0];
70 expect(observed?.Status).toBe("active");
71 expect(observed?.Authentication?.Type).toBe("password");
72
73 // Update the access string in place (name unchanged).
74 const { user: updated } = yield* stack.deploy(
75 Effect.gen(function* () {
76 const user = yield* User("AppUser", {
77 authenticationMode: {
78 type: "password",
79 passwords: [TEST_PASSWORD],
80 },
81 accessString: "on ~app:* +@read",
82 tags: { fixture: "memorydb-user", env: "test" },
83 });
84 return { user };
85 }),
86 );
87 expect(updated.userName).toBe(user.userName);
88 expect(updated.accessString).toContain("~app:*");
89
90 const redescribed = yield* memorydb.describeUsers({
91 UserName: user.userName,
92 });
93 expect(redescribed.Users?.[0]?.AccessString).toContain("~app:*");
94
95 yield* stack.destroy();
96 yield* assertDeletingOrGone(user.userName);
97 }),
98 { tags: ["provider:aws", "provider:aws:memorydb", "live"], timeout: 240_000 },
99);