1import * as AIOps from "@/AWS/AIOps";
2import { Role } from "@/AWS/IAM/Role.ts";
3import * as Lambda from "@/AWS/Lambda";
4import * as Duration from "effect/Duration";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import { HttpServerRequest } from "effect/http/HttpServerRequest";
8import * as HttpServerResponse from "effect/http/HttpServerResponse";
9import path from "pathe";
10
11const main = path.resolve(import.meta.dirname, "handler.ts");
12
13export class AIOpsTestFunction extends Lambda.Function<Lambda.Function>()(
14 "AIOpsTestFunction",
15) {}
16
17export default AIOpsTestFunction.make(
18 {
19 main,
20 functionUrl: true,
21 timeout: Duration.seconds(30),
22 },
23 Effect.gen(function* () {
24 // The one-per-Region investigation group the group-scoped bindings are
25 // bound to. The role is what CloudWatch investigations assumes to read
26 // telemetry — required at create time.
27 const role = yield* Role("AIOpsBindingRole", {
28 assumeRolePolicyDocument: {
29 Version: "2012-10-17",
30 Statement: [
31 {
32 Effect: "Allow",
33 Principal: { Service: "aiops.amazonaws.com" },
34 Action: ["sts:AssumeRole"],
35 },
36 ],
37 },
38 managedPolicyArns: ["arn:aws:iam::aws:policy/AIOpsAssistantPolicy"],
39 });
40 const group = yield* AIOps.InvestigationGroup("BindingGroup", {
41 roleArn: role.roleArn,
42 retention: "7 days",
43 tags: { Purpose: "bindings-test" },
44 });
45
46 const getInvestigationGroup = yield* AIOps.GetInvestigationGroup(group);
47 const getInvestigationGroupPolicy =
48 yield* AIOps.GetInvestigationGroupPolicy(group);
49 const listTagsForResource = yield* AIOps.ListTagsForResource(group);
50 const listInvestigationGroups = yield* AIOps.ListInvestigationGroups();
51
52 const bound = {
53 getInvestigationGroup,
54 getInvestigationGroupPolicy,
55 listTagsForResource,
56 listInvestigationGroups,
57 };
58
59 return {
60 fetch: Effect.gen(function* () {
61 const request = yield* HttpServerRequest;
62 const url = new URL(request.originalUrl);
63 const pathname = url.pathname;
64
65 if (request.method === "GET" && pathname === "/bindings") {
66 return yield* HttpServerResponse.json({
67 bound: Object.keys(bound),
68 });
69 }
70
71 // Group-scoped read: the group ARN is injected from the binding.
72 if (request.method === "GET" && pathname === "/group") {
73 const detail = yield* getInvestigationGroup();
74 return yield* HttpServerResponse.json({
75 name: detail.name,
76 arn: detail.arn,
77 retentionInDays: detail.retentionInDays,
78 });
79 }
80
81 // Group-scoped policy read: no policy is attached to the fixture's
82 // group, so the typed ResourceNotFoundException proves the grant and
83 // ARN injection end-to-end.
84 if (request.method === "GET" && pathname === "/policy") {
85 const policy = yield* getInvestigationGroupPolicy().pipe(
86 Effect.map((r) => r.policy),
87 Effect.catchTag("ResourceNotFoundException", () =>
88 Effect.succeed(undefined),
89 ),
90 );
91 return yield* HttpServerResponse.json({
92 hasPolicy: policy !== undefined,
93 });
94 }
95
96 // Group-scoped tag read.
97 if (request.method === "GET" && pathname === "/tags") {
98 const { tags } = yield* listTagsForResource();
99 return yield* HttpServerResponse.json({ tags: tags ?? {} });
100 }
101
102 // Account-level list: at most one group exists per Region, and it is
103 // the fixture's own group.
104 if (request.method === "GET" && pathname === "/groups") {
105 const { investigationGroups } = yield* listInvestigationGroups();
106 return yield* HttpServerResponse.json({
107 groupArns: (investigationGroups ?? []).map((g) => g.arn),
108 });
109 }
110
111 return yield* HttpServerResponse.json(
112 { error: "Not found", method: request.method, pathname },
113 { status: 404 },
114 );
115 }).pipe(Effect.orDie),
116 };
117 }).pipe(
118 Effect.provide(
119 Layer.mergeAll(
120 AIOps.GetInvestigationGroupHttp,
121 AIOps.GetInvestigationGroupPolicyHttp,
122 AIOps.ListTagsForResourceHttp,
123 AIOps.ListInvestigationGroupsHttp,
124 ),
125 ),
126 ),
127);