1import * as AWS from "@/AWS";
2import * as Test from "@/Test/Alchemy";
3import * as agw2 from "@distilled.cloud/aws/apigatewayv2";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6
7const { test } = Test.make({ providers: AWS.providers() });
8
9// CreateAuthorizer validates that the issuer serves a live
10// `/.well-known/openid-configuration` discovery document (probed:
11// BadRequestException "Issuer must have a valid discovery endpoint" for
12// placeholder domains). Tokens are only validated at request time, so any
13// stable public OIDC issuer exercises the CRUD lifecycle.
14const ISSUER = "https://accounts.google.com";
15
16test.provider(
17 "create, update, delete JWT authorizer",
18 (stack) =>
19 Effect.gen(function* () {
20 yield* stack.destroy();
21
22 const deployAuthorizer = (audience: string[]) =>
23 stack.deploy(
24 Effect.gen(function* () {
25 const api = yield* AWS.ApiGatewayV2.Api("AuthApi", {});
26 const authorizer = yield* AWS.ApiGatewayV2.Authorizer(
27 "JwtAuthorizer",
28 {
29 api,
30 authorizerType: "JWT",
31 identitySource: ["$request.header.Authorization"],
32 jwtConfiguration: {
33 Issuer: ISSUER,
34 Audience: audience,
35 },
36 },
37 );
38 return {
39 apiId: api.apiId,
40 authorizerId: authorizer.authorizerId,
41 name: authorizer.name,
42 };
43 }),
44 );
45
46 const out = yield* deployAuthorizer(["aud-1"]);
47 expect(out.authorizerId).toBeTruthy();
48
49 // Out-of-band verification via distilled.
50 const remote = yield* agw2.getAuthorizer({
51 ApiId: out.apiId,
52 AuthorizerId: out.authorizerId,
53 });
54 expect(remote.AuthorizerType).toBe("JWT");
55 expect(remote.JwtConfiguration?.Issuer).toBe(ISSUER);
56 expect(remote.JwtConfiguration?.Audience).toEqual(["aud-1"]);
57
58 // Update the audience in place (same authorizerId).
59 const updated = yield* deployAuthorizer(["aud-1", "aud-2"]);
60 expect(updated.authorizerId).toBe(out.authorizerId);
61
62 const afterUpdate = yield* agw2.getAuthorizer({
63 ApiId: out.apiId,
64 AuthorizerId: out.authorizerId,
65 });
66 expect(afterUpdate.JwtConfiguration?.Audience).toEqual([
67 "aud-1",
68 "aud-2",
69 ]);
70
71 yield* stack.destroy();
72
73 const gone = yield* agw2
74 .getAuthorizer({
75 ApiId: out.apiId,
76 AuthorizerId: out.authorizerId,
77 })
78 .pipe(
79 Effect.map(() => "still-exists" as const),
80 Effect.catchTag("NotFoundException", () =>
81 Effect.succeed("deleted" as const),
82 ),
83 );
84 expect(gone).toBe("deleted");
85 }),
86 {
87 tags: ["provider:aws", "provider:aws:apigatewayv2", "live"],
88 timeout: 240_000,
89 },
90);