1import * as AWS from "@/AWS";
2import { ResolverEndpoint } from "@/AWS/Route53Resolver";
3import * as Test from "@/Test/Alchemy";
4import * as r53r from "@distilled.cloud/aws/route53resolver";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { assertEndpointDeleting, defaultNetwork } from "./helpers.ts";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11test.provider(
12 "create, update tags, delete an inbound resolver endpoint",
13 (stack) =>
14 Effect.gen(function* () {
15 yield* stack.destroy();
16
17 const net = yield* defaultNetwork;
18 const make = (tags: Record<string, string>) =>
19 Effect.gen(function* () {
20 const endpoint = yield* ResolverEndpoint("Inbound", {
21 direction: "INBOUND",
22 securityGroupIds: [net.securityGroupId],
23 ipAddresses: net.subnetIds.map((subnetId) => ({ subnetId })),
24 tags,
25 });
26 return { endpoint };
27 });
28
29 const { endpoint } = yield* stack.deploy(
30 make({ fixture: "r53r-endpoint" }),
31 );
32
33 expect(endpoint.resolverEndpointId).toMatch(/^rslvr-in-/);
34 expect(endpoint.direction).toBe("INBOUND");
35 expect(endpoint.hostVpcId).toBe(net.vpcId);
36 expect(endpoint.resolverEndpointArn).toContain(":resolver-endpoint/");
37
38 // Out-of-band: the endpoint is OPERATIONAL (reconcile waits for it)
39 // and carries both the fixture tag and the internal Alchemy tags.
40 const observed = yield* r53r.getResolverEndpoint({
41 ResolverEndpointId: endpoint.resolverEndpointId,
42 });
43 expect(observed.ResolverEndpoint?.Status).toBe("OPERATIONAL");
44 expect(observed.ResolverEndpoint?.Direction).toBe("INBOUND");
45 expect(observed.ResolverEndpoint?.IpAddressCount).toBe(2);
46 const tags = yield* r53r.listTagsForResource({
47 ResourceArn: endpoint.resolverEndpointArn,
48 });
49 const tagRecord = Object.fromEntries(
50 (tags.Tags ?? []).map((t) => [t.Key, t.Value]),
51 );
52 expect(tagRecord.fixture).toBe("r53r-endpoint");
53 expect(tagRecord["alchemy::id"]).toBe("Inbound");
54
55 // In-place tag update — same endpoint (no replacement).
56 const { endpoint: updated } = yield* stack.deploy(make({ team: "dns" }));
57 expect(updated.resolverEndpointId).toBe(endpoint.resolverEndpointId);
58 const tagsAfter = yield* r53r.listTagsForResource({
59 ResourceArn: endpoint.resolverEndpointArn,
60 });
61 const tagRecordAfter = Object.fromEntries(
62 (tagsAfter.Tags ?? []).map((t) => [t.Key, t.Value]),
63 );
64 expect(tagRecordAfter.team).toBe("dns");
65 expect(tagRecordAfter.fixture).toBeUndefined();
66 expect(tagRecordAfter["alchemy::id"]).toBe("Inbound");
67
68 yield* stack.destroy();
69 yield* assertEndpointDeleting(endpoint.resolverEndpointId);
70 }),
71 {
72 tags: [
73 "provider:aws",
74 "provider:aws:ec2",
75 "provider:aws:route53resolver",
76 "live",
77 ],
78 timeout: 220_000,
79 },
80);