1import * as AWS from "@/AWS";
2import { ServerCertificate, VirtualMFADevice } from "@/AWS/IAM";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as IAM from "@distilled.cloud/aws/iam";
6import { describe, expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { testCertificateBody, testPrivateKey } from "./fixtures.ts";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12describe(
13 "AWS.IAM device and server certificate resources",
14 { tags: ["provider:aws", "provider:aws:iam", "live"] },
15 () => {
16 test.provider("create, update, and delete a server certificate", (stack) =>
17 Effect.gen(function* () {
18 yield* stack.destroy();
19
20 const certificate = yield* stack.deploy(
21 Effect.gen(function* () {
22 return yield* ServerCertificate("ServerCertificate", {
23 certificateBody: testCertificateBody,
24 privateKey: testPrivateKey,
25 tags: {
26 env: "test",
27 },
28 });
29 }),
30 );
31
32 const created = yield* IAM.getServerCertificate({
33 ServerCertificateName: certificate.serverCertificateName,
34 });
35 expect(
36 created.ServerCertificate.ServerCertificateMetadata
37 .ServerCertificateName,
38 ).toBe(certificate.serverCertificateName);
39
40 yield* stack.deploy(
41 Effect.gen(function* () {
42 return yield* ServerCertificate("ServerCertificate", {
43 certificateBody: testCertificateBody,
44 privateKey: testPrivateKey,
45 tags: {
46 env: "prod",
47 },
48 });
49 }),
50 );
51
52 const updatedTags = yield* IAM.listServerCertificateTags({
53 ServerCertificateName: certificate.serverCertificateName,
54 });
55 expect(
56 Object.fromEntries(
57 (updatedTags.Tags ?? []).map((tag) => [tag.Key, tag.Value]),
58 ),
59 ).toMatchObject({
60 env: "prod",
61 });
62
63 yield* stack.destroy();
64
65 const deleted = yield* IAM.getServerCertificate({
66 ServerCertificateName: certificate.serverCertificateName,
67 }).pipe(Effect.option);
68 expect(deleted._tag).toBe("None");
69 }),
70 );
71
72 test.provider("list enumerates the deployed server certificate", (stack) =>
73 Effect.gen(function* () {
74 yield* stack.destroy();
75
76 const certificate = yield* stack.deploy(
77 Effect.gen(function* () {
78 return yield* ServerCertificate("ListServerCertificate", {
79 certificateBody: testCertificateBody,
80 privateKey: testPrivateKey,
81 tags: {
82 env: "test",
83 },
84 });
85 }),
86 );
87
88 const provider = yield* Provider.findProvider(ServerCertificate);
89 const all = yield* provider.list();
90
91 const found = all.find(
92 (cert) =>
93 cert.serverCertificateArn === certificate.serverCertificateArn,
94 );
95 expect(found).toBeDefined();
96 expect(found?.serverCertificateName).toBe(
97 certificate.serverCertificateName,
98 );
99 expect(found?.certificateBody).toBe(testCertificateBody);
100 expect(found?.tags).toMatchObject({ env: "test" });
101
102 yield* stack.destroy();
103 }),
104 );
105
106 test.provider(
107 "create, update, and delete an unassigned virtual MFA device",
108 (stack) =>
109 Effect.gen(function* () {
110 yield* stack.destroy();
111
112 const device = yield* stack.deploy(
113 Effect.gen(function* () {
114 return yield* VirtualMFADevice("VirtualMfaDevice", {
115 tags: {
116 env: "test",
117 },
118 });
119 }),
120 );
121
122 expect(device.base32StringSeed).toBeDefined();
123 expect(device.qrCodePNG).toBeDefined();
124
125 const created = yield* IAM.listVirtualMFADevices({
126 AssignmentStatus: "Unassigned",
127 });
128 expect(
129 created.VirtualMFADevices.some(
130 (entry) => entry.SerialNumber === device.serialNumber,
131 ),
132 ).toBe(true);
133
134 yield* stack.deploy(
135 Effect.gen(function* () {
136 return yield* VirtualMFADevice("VirtualMfaDevice", {
137 tags: {
138 env: "prod",
139 },
140 });
141 }),
142 );
143
144 const updatedTags = yield* IAM.listMFADeviceTags({
145 SerialNumber: device.serialNumber,
146 });
147 expect(
148 Object.fromEntries(
149 (updatedTags.Tags ?? []).map((tag) => [tag.Key, tag.Value]),
150 ),
151 ).toMatchObject({
152 env: "prod",
153 });
154
155 yield* stack.destroy();
156
157 const deleted = yield* IAM.listVirtualMFADevices({
158 AssignmentStatus: "Unassigned",
159 }).pipe(Effect.option);
160 expect(
161 deleted._tag === "None" ||
162 !deleted.value.VirtualMFADevices.some(
163 (entry) => entry.SerialNumber === device.serialNumber,
164 ),
165 ).toBe(true);
166 }),
167 );
168 },
169);