1import * as AWS from "@/AWS";
2import { PermissionSet } from "@/AWS/IdentityCenter";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as ssoAdmin from "@distilled.cloud/aws/sso-admin";
6import { expect } from "alchemy-test";
7import * as Duration from "effect/Duration";
8import * as Effect from "effect/Effect";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12// Requires an enabled IAM Identity Center (SSO) instance in the testing
13// account. Without one, `resolveInstance` fails with:
14// Error: "Unable to resolve a single visible Identity Center instance;
15// pass instanceArn explicitly"
16// The testing account is an ORGANIZATION MANAGEMENT account, where
17// `CreateInstance` is rejected with a typed
18// ValidationException: "Organization management account is not allowed to
19// perform the operation."
20// and organization instances can only be enabled via the console — so an
21// instance cannot be provisioned programmatically. Gate the live lifecycle
22// behind ALCHEMY_TEST_IDENTITY_CENTER=1 so an entitled account runs it
23// unchanged.
24const SKIP_IDENTITY_CENTER = !process.env.ALCHEMY_TEST_IDENTITY_CENTER;
25
26test.provider.skipIf(SKIP_IDENTITY_CENTER)(
27 "lifecycle round-trips sessionDuration and list enumerates the set",
28 (stack) =>
29 Effect.gen(function* () {
30 yield* stack.destroy();
31
32 // Create with a string Duration.Input — must land on the wire as
33 // the canonical ISO-8601 string.
34 const permissionSet = yield* stack.deploy(
35 Effect.gen(function* () {
36 return yield* PermissionSet("ListPermissionSet", {
37 name: "alchemy-test-list-permission-set",
38 description: "list() coverage test",
39 sessionDuration: "1 hour",
40 });
41 }),
42 );
43
44 expect(permissionSet.sessionDuration).toBe("PT1H");
45
46 // Out-of-band wire verification via distilled.
47 const described = yield* ssoAdmin.describePermissionSet({
48 InstanceArn: permissionSet.instanceArn,
49 PermissionSetArn: permissionSet.permissionSetArn,
50 });
51 expect(described.PermissionSet?.SessionDuration).toBe("PT1H");
52
53 const provider = yield* Provider.findProvider(PermissionSet);
54 const all = yield* provider.list();
55
56 const found = all.find(
57 (p) => p.permissionSetArn === permissionSet.permissionSetArn,
58 );
59 expect(found).toBeDefined();
60 expect(found?.name).toBe(permissionSet.name);
61 expect(found?.instanceArn).toBe(permissionSet.instanceArn);
62
63 // Update with a Duration object — exercises the non-string
64 // Duration.Input path and the updatePermissionSet sync branch.
65 const updated = yield* stack.deploy(
66 Effect.gen(function* () {
67 return yield* PermissionSet("ListPermissionSet", {
68 name: "alchemy-test-list-permission-set",
69 description: "list() coverage test",
70 sessionDuration: Duration.minutes(90),
71 });
72 }),
73 );
74
75 expect(updated.permissionSetArn).toBe(permissionSet.permissionSetArn);
76 expect(updated.sessionDuration).toBe("PT1H30M");
77
78 const redescribed = yield* ssoAdmin.describePermissionSet({
79 InstanceArn: updated.instanceArn,
80 PermissionSetArn: updated.permissionSetArn,
81 });
82 expect(redescribed.PermissionSet?.SessionDuration).toBe("PT1H30M");
83
84 yield* stack.destroy();
85 }),
86 {
87 tags: ["provider:aws", "provider:aws:identitycenter", "live"],
88 timeout: 180_000,
89 },
90);