1import * as AWS from "@/AWS";
2import { SecurityConfiguration } from "@/AWS/EMR/SecurityConfiguration.ts";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as emr from "@distilled.cloud/aws/emr";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11const configName = "alchemy-test-emr-security-configuration";
12
13// Ungated typed-error probe: proves the distilled emr error union carries the
14// SecurityConfigurationNotFound tag this provider's read/delete paths depend
15// on (EMR overloads InvalidRequestException with a message-only distinction).
16test.provider(
17 "describeSecurityConfiguration on a nonexistent name fails with SecurityConfigurationNotFound",
18 () =>
19 Effect.gen(function* () {
20 const error = yield* Effect.flip(
21 emr.describeSecurityConfiguration({
22 Name: "alchemy-emr-nonexistent-probe",
23 }),
24 );
25 expect(error._tag).toBe("SecurityConfigurationNotFound");
26 }),
27 { tags: ["provider:aws", "provider:aws:emr", "live"] },
28);
29
30const imdsDocument = {
31 InstanceMetadataServiceConfiguration: {
32 MinimumInstanceMetadataServiceVersion: 2,
33 HttpPutResponseHopLimit: 1,
34 },
35};
36
37const encryptionDocument = {
38 EncryptionConfiguration: {
39 EnableInTransitEncryption: false,
40 EnableAtRestEncryption: false,
41 },
42};
43
44test.provider(
45 "lifecycle: create, update content in place, replace on rename, destroy",
46 (stack) =>
47 Effect.gen(function* () {
48 yield* stack.destroy();
49
50 // Create.
51 const deployed = yield* stack.deploy(
52 Effect.gen(function* () {
53 return yield* SecurityConfiguration("Config", {
54 securityConfigurationName: configName,
55 securityConfiguration: imdsDocument,
56 });
57 }),
58 );
59 expect(deployed.securityConfigurationName).toBe(configName);
60
61 // Out-of-band verification via distilled.
62 const created = yield* emr.describeSecurityConfiguration({
63 Name: configName,
64 });
65 expect(JSON.parse(created.SecurityConfiguration ?? "{}")).toEqual(
66 imdsDocument,
67 );
68
69 // Canonical list() coverage.
70 const provider = yield* Provider.findProvider(SecurityConfiguration);
71 const all = yield* provider.list();
72 expect(all.some((c) => c.securityConfigurationName === configName)).toBe(
73 true,
74 );
75
76 // Update content in place — the document is immutable in EMR, so the
77 // provider converges by deleting and recreating under the same name
78 // (no replacement from the engine's perspective).
79 yield* stack.deploy(
80 Effect.gen(function* () {
81 return yield* SecurityConfiguration("Config", {
82 securityConfigurationName: configName,
83 securityConfiguration: encryptionDocument,
84 });
85 }),
86 );
87 const updated = yield* emr.describeSecurityConfiguration({
88 Name: configName,
89 });
90 expect(JSON.parse(updated.SecurityConfiguration ?? "{}")).toEqual(
91 encryptionDocument,
92 );
93
94 // Rename — diff returns replace; the new name exists, the old is gone.
95 yield* stack.deploy(
96 Effect.gen(function* () {
97 return yield* SecurityConfiguration("Config", {
98 securityConfigurationName: `${configName}-renamed`,
99 securityConfiguration: encryptionDocument,
100 });
101 }),
102 );
103 const renamed = yield* emr.describeSecurityConfiguration({
104 Name: `${configName}-renamed`,
105 });
106 expect(renamed.Name).toBe(`${configName}-renamed`);
107 const oldGone = yield* Effect.flip(
108 emr.describeSecurityConfiguration({ Name: configName }),
109 );
110 expect(oldGone._tag).toBe("SecurityConfigurationNotFound");
111
112 // Destroy — verify gone out of band.
113 yield* stack.destroy();
114 const afterDestroy = yield* Effect.flip(
115 emr.describeSecurityConfiguration({ Name: `${configName}-renamed` }),
116 );
117 expect(afterDestroy._tag).toBe("SecurityConfigurationNotFound");
118 }),
119 { tags: ["provider:aws", "provider:aws:emr", "live"], timeout: 120_000 },
120);