1import * as AWS from "@/AWS";
2import * as Test from "@/Test/Alchemy";
3import * as appsync from "@distilled.cloud/aws/appsync";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Result from "effect/Result";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10const SCHEMA = `
11type Query {
12 hello: String
13}
14schema { query: Query }
15`;
16
17// AppSync custom domains require an ACM certificate in us-east-1
18// (CloudFront-backed). The full lifecycle is gated on a standing cert;
19// the ungated probe below pins the typed rejection forever.
20const domainName = process.env.AWS_TEST_APPSYNC_DOMAIN_NAME;
21const certificateArn = process.env.AWS_TEST_APPSYNC_DOMAIN_CERT_ARN;
22
23test.provider(
24 "createDomainName rejects a certificate that does not exist (typed probe)",
25 () =>
26 Effect.gen(function* () {
27 const result = yield* Effect.result(
28 appsync.createDomainName({
29 domainName: "alchemy-appsync-probe.example.com",
30 certificateArn:
31 "arn:aws:acm:us-east-1:000000000000:certificate/00000000-0000-0000-0000-000000000000",
32 }),
33 );
34 expect(Result.isFailure(result)).toBe(true);
35 if (Result.isFailure(result)) {
36 // Cross-account/nonexistent cert surfaces as the typed
37 // BadRequestException (or AccessDenied depending on validation
38 // order) — never an untyped catch-all.
39 expect(["BadRequestException", "AccessDeniedException"]).toContain(
40 result.failure._tag,
41 );
42 }
43 }),
44 { tags: ["provider:aws", "provider:aws:appsync", "live"], timeout: 60_000 },
45);
46
47test.provider.skipIf(!domainName || !certificateArn)(
48 "custom domain + api association lifecycle (gated)",
49 (stack) =>
50 Effect.gen(function* () {
51 yield* stack.destroy();
52
53 const out = yield* stack.deploy(
54 Effect.gen(function* () {
55 const api = yield* AWS.AppSync.GraphqlApi("DomainApi", {
56 schema: SCHEMA,
57 });
58 const domain = yield* AWS.AppSync.DomainName("Domain", {
59 domainName: domainName!,
60 certificateArn: certificateArn!,
61 description: "alchemy test domain",
62 });
63 yield* AWS.AppSync.ApiAssociation("Assoc", { domain, api });
64 return {
65 apiId: api.apiId,
66 domainName: domain.domainName,
67 appsyncDomainName: domain.appsyncDomainName,
68 };
69 }),
70 );
71
72 expect(out.appsyncDomainName).toContain("cloudfront");
73
74 const remote = yield* appsync.getDomainName({
75 domainName: out.domainName,
76 });
77 expect(remote.domainNameConfig?.certificateArn).toBe(certificateArn);
78
79 const association = yield* appsync.getApiAssociation({
80 domainName: out.domainName,
81 });
82 expect(association.apiAssociation?.apiId).toBe(out.apiId);
83
84 yield* stack.destroy();
85
86 const gone = yield* appsync
87 .getDomainName({ domainName: out.domainName })
88 .pipe(
89 Effect.map(() => false),
90 Effect.catchTag("NotFoundException", () => Effect.succeed(true)),
91 );
92 expect(gone).toBe(true);
93 }),
94 { tags: ["provider:aws", "provider:aws:appsync", "live"], timeout: 240_000 },
95);