1import * as AWS from "@/AWS";
2import { UserPool, UserPoolClient } from "@/AWS/Cognito";
3import * as Test from "@/Test/Alchemy";
4import * as cip from "@distilled.cloud/aws/cognito-identity-provider";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10test.provider(
11 "create, update auth flows and validity, replace on generateSecret",
12 (stack) =>
13 Effect.gen(function* () {
14 yield* stack.destroy();
15
16 const outputs = yield* stack.deploy(
17 Effect.gen(function* () {
18 const pool = yield* UserPool("ClientTestPool", {});
19 const client = yield* UserPoolClient("Client", {
20 userPoolId: pool.userPoolId,
21 explicitAuthFlows: [
22 "ALLOW_USER_PASSWORD_AUTH",
23 "ALLOW_REFRESH_TOKEN_AUTH",
24 ],
25 });
26 return { pool, client };
27 }),
28 );
29
30 expect(outputs.client.clientId).toBeTruthy();
31 expect(outputs.client.clientSecret).toBeUndefined();
32 expect(outputs.client.userPoolId).toBe(outputs.pool.userPoolId);
33
34 // out-of-band verification via distilled
35 const created = yield* cip.describeUserPoolClient({
36 UserPoolId: outputs.pool.userPoolId,
37 ClientId: outputs.client.clientId,
38 });
39 expect(
40 [...(created.UserPoolClient?.ExplicitAuthFlows ?? [])].sort(),
41 ).toEqual(["ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_PASSWORD_AUTH"]);
42
43 // mutate token validity in place
44 const updated = yield* stack.deploy(
45 Effect.gen(function* () {
46 const pool = yield* UserPool("ClientTestPool", {});
47 const client = yield* UserPoolClient("Client", {
48 userPoolId: pool.userPoolId,
49 explicitAuthFlows: [
50 "ALLOW_USER_PASSWORD_AUTH",
51 "ALLOW_REFRESH_TOKEN_AUTH",
52 ],
53 accessTokenValidity: 30,
54 idTokenValidity: 30,
55 tokenValidityUnits: { accessToken: "minutes", idToken: "minutes" },
56 });
57 return { pool, client };
58 }),
59 );
60 expect(updated.client.clientId).toBe(outputs.client.clientId);
61
62 const afterUpdate = yield* cip.describeUserPoolClient({
63 UserPoolId: outputs.pool.userPoolId,
64 ClientId: outputs.client.clientId,
65 });
66 expect(afterUpdate.UserPoolClient?.AccessTokenValidity).toBe(30);
67 expect(afterUpdate.UserPoolClient?.TokenValidityUnits?.AccessToken).toBe(
68 "minutes",
69 );
70
71 // generateSecret is immutable ⇒ replacement with a fresh client id
72 const replaced = yield* stack.deploy(
73 Effect.gen(function* () {
74 const pool = yield* UserPool("ClientTestPool", {});
75 const client = yield* UserPoolClient("Client", {
76 userPoolId: pool.userPoolId,
77 generateSecret: true,
78 explicitAuthFlows: [
79 "ALLOW_USER_PASSWORD_AUTH",
80 "ALLOW_REFRESH_TOKEN_AUTH",
81 ],
82 });
83 return { pool, client };
84 }),
85 );
86 expect(replaced.client.clientId).not.toBe(outputs.client.clientId);
87 expect(replaced.client.clientSecret).toBeTruthy();
88
89 yield* stack.destroy();
90 const gone = yield* cip
91 .describeUserPoolClient({
92 UserPoolId: outputs.pool.userPoolId,
93 ClientId: replaced.client.clientId,
94 })
95 .pipe(
96 Effect.map(() => false),
97 Effect.catchTag("ResourceNotFoundException", () =>
98 Effect.succeed(true),
99 ),
100 );
101 expect(gone).toBe(true);
102 }),
103 { tags: ["provider:aws", "provider:aws:cognito", "live"], timeout: 120_000 },
104);