| 1 | import * as AWS from "@/AWS"; |
| 2 | import { Function } from "@/AWS/CloudFront"; |
| 3 | import * as Provider from "@/Provider"; |
| 4 | import * as Test from "@/Test/Alchemy"; |
| 5 | import * as cloudfront from "@distilled.cloud/aws/cloudfront"; |
| 6 | import { expect } from "alchemy-test"; |
| 7 | import * as Effect from "effect/Effect"; |
| 8 | import * as Schedule from "effect/Schedule"; |
| 9 | |
| 10 | const { test } = Test.make({ providers: AWS.providers() }); |
| 11 | |
| 12 | test.provider( |
| 13 | "create and delete a CloudFront Function with key value store associations", |
| 14 | (stack) => |
| 15 | Effect.gen(function* () { |
| 16 | yield* stack.destroy(); |
| 17 | |
| 18 | const deployed = yield* stack.deploy( |
| 19 | Effect.gen(function* () { |
| 20 | const store = yield* AWS.CloudFront.KeyValueStore("RequestStore", { |
| 21 | comment: "request metadata", |
| 22 | }); |
| 23 | const fn = yield* AWS.CloudFront.Function("RequestFn", { |
| 24 | comment: "request handler", |
| 25 | keyValueStoreArns: [store.keyValueStoreArn], |
| 26 | code: `async function handler(event) { |
| 27 | return event.request; |
| 28 | }`, |
| 29 | }); |
| 30 | return { store, fn }; |
| 31 | }), |
| 32 | ); |
| 33 | |
| 34 | const current = yield* cloudfront.describeFunction({ |
| 35 | Name: deployed.fn.functionName, |
| 36 | Stage: "LIVE", |
| 37 | }); |
| 38 | expect(current.FunctionSummary?.Name).toEqual(deployed.fn.functionName); |
| 39 | expect( |
| 40 | current.FunctionSummary?.FunctionConfig.KeyValueStoreAssociations |
| 41 | ?.Items?.[0]?.KeyValueStoreARN, |
| 42 | ).toEqual(deployed.store.keyValueStoreArn); |
| 43 | |
| 44 | yield* stack.destroy(); |
| 45 | yield* assertFunctionDeleted(deployed.fn.functionName); |
| 46 | }), |
| 47 | { |
| 48 | tags: ["provider:aws", "provider:aws:cloudfront", "live"], |
| 49 | timeout: 300_000, |
| 50 | }, |
| 51 | ); |
| 52 | |
| 53 | // `FunctionConfig.Comment` is patched optional in |
| 54 | // submodules/distilled/packages/aws/patches/cloudfront.json — CloudFront omits `Comment` |
| 55 | // in `listFunctions` responses for functions created without one, and a |
| 56 | // comment-less function anywhere in the account used to fail the decode with |
| 57 | // `SchemaError: Missing key ... FunctionConfig.Comment`. |
| 58 | test.provider( |
| 59 | "list enumerates the deployed CloudFront Function", |
| 60 | (stack) => |
| 61 | Effect.gen(function* () { |
| 62 | yield* stack.destroy(); |
| 63 | |
| 64 | const deployed = yield* stack.deploy( |
| 65 | Effect.gen(function* () { |
| 66 | return yield* AWS.CloudFront.Function("ListFn", { |
| 67 | comment: "list handler", |
| 68 | code: `async function handler(event) { |
| 69 | return event.request; |
| 70 | }`, |
| 71 | }); |
| 72 | }), |
| 73 | ); |
| 74 | |
| 75 | const provider = yield* Provider.findProvider(Function); |
| 76 | const all = yield* provider.list(); |
| 77 | |
| 78 | expect(all.some((fn) => fn.functionName === deployed.functionName)).toBe( |
| 79 | true, |
| 80 | ); |
| 81 | |
| 82 | yield* stack.destroy(); |
| 83 | yield* assertFunctionDeleted(deployed.functionName); |
| 84 | }), |
| 85 | { |
| 86 | tags: ["provider:aws", "provider:aws:cloudfront", "live"], |
| 87 | timeout: 300_000, |
| 88 | }, |
| 89 | ); |
| 90 | |
| 91 | const assertFunctionDeleted = (name: string) => |
| 92 | cloudfront |
| 93 | .describeFunction({ |
| 94 | Name: name, |
| 95 | Stage: "LIVE", |
| 96 | }) |
| 97 | .pipe( |
| 98 | Effect.flatMap(() => Effect.fail(new Error("FunctionStillExists"))), |
| 99 | Effect.catchTag("NoSuchFunctionExists", () => Effect.void), |
| 100 | Effect.retry({ |
| 101 | while: (error) => |
| 102 | error instanceof Error && error.message === "FunctionStillExists", |
| 103 | schedule: Schedule.max([ |
| 104 | Schedule.fixed("5 seconds"), |
| 105 | Schedule.recurs(24), |
| 106 | ]), |
| 107 | }), |
| 108 | ); |