t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/AWS/MQ/bindings-handler.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

173 lines
5.9 KiB
TypeScript

import * as Lambda from "@/AWS/Lambda";
import * as MQ from "@/AWS/MQ";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Redacted from "effect/Redacted";
import { HttpServerRequest } from "effect/http/HttpServerRequest";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
import path from "pathe";
const main = path.resolve(import.meta.dirname, "bindings-handler.ts");
export class MQBindingsFunction extends Lambda.Function<Lambda.Function>()(
"MQBindingsFunction",
) {}
export default MQBindingsFunction.make(
{
main,
functionUrl: true,
},
Effect.gen(function* () {
// A single-instance mq.t3.micro ActiveMQ broker is the cheapest topology
// and the only engine whose user APIs (CreateUser/UpdateUser/...) are
// supported. Omitting engineVersion lets AWS pick the current default.
const broker = yield* MQ.Broker("BindingsBroker", {
engineType: "ACTIVEMQ",
hostInstanceType: "mq.t3.micro",
deploymentMode: "SINGLE_INSTANCE",
publiclyAccessible: true,
users: [
{
username: "alchemyadmin",
password: Redacted.make("SuperSecretPassw0rd!"),
},
],
});
const describeBroker = yield* MQ.DescribeBroker(broker);
const rebootBroker = yield* MQ.RebootBroker(broker);
const promote = yield* MQ.Promote(broker);
const createUser = yield* MQ.CreateUser(broker);
const updateUser = yield* MQ.UpdateUser(broker);
const deleteUser = yield* MQ.DeleteUser(broker);
const describeUser = yield* MQ.DescribeUser(broker);
const listUsers = yield* MQ.ListUsers(broker);
const listBrokers = yield* MQ.ListBrokers();
const bound = {
describeBroker,
rebootBroker,
promote,
createUser,
updateUser,
deleteUser,
describeUser,
listUsers,
listBrokers,
};
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
const url = new URL(request.originalUrl);
const pathname = url.pathname;
if (request.method === "GET" && pathname === "/bindings") {
return yield* HttpServerResponse.json({
bound: Object.keys(bound),
});
}
if (request.method === "GET" && pathname === "/broker") {
// BrokerId injection scopes the call to the bound broker.
const response = yield* describeBroker();
return yield* HttpServerResponse.json({
brokerName: response.BrokerName,
brokerState: response.BrokerState,
});
}
if (request.method === "GET" && pathname === "/brokers") {
const response = yield* listBrokers();
return yield* HttpServerResponse.json({
count: (response.BrokerSummaries ?? []).length,
});
}
if (request.method === "GET" && pathname === "/users") {
const response = yield* listUsers();
return yield* HttpServerResponse.json({
count: (response.Users ?? []).length,
});
}
if (request.method === "POST" && pathname === "/users") {
// Full staged-user round-trip: create stages a CREATE pending
// change, describe reads it back, delete stages the removal — the
// broker never needs a reboot, so the test stays fast and the
// stack destroys cleanly.
yield* createUser({
Username: "alchemytenant",
Password: Redacted.make("AnotherSecretPassw0rd!"),
});
const described = yield* describeUser({
Username: "alchemytenant",
});
yield* deleteUser({ Username: "alchemytenant" });
return yield* HttpServerResponse.json({
created: true,
describedUsername: described.Username,
pendingChange: described.Pending?.PendingChange,
deleted: true,
});
}
if (
request.method === "POST" &&
pathname === "/users/typed-not-found"
) {
// UpdateUser on a nonexistent user round-trips the typed
// NotFoundException — an IAM gap would surface AccessDenied (500),
// so the typed tag proves grant + injection end-to-end.
const typed = yield* updateUser({
Username: "alchemynonexistentuser",
ConsoleAccess: true,
}).pipe(
Effect.map(() => false),
Effect.catchTag("NotFoundException", () => Effect.succeed(true)),
);
return yield* HttpServerResponse.json({ typed });
}
if (request.method === "POST" && pathname === "/promote") {
// The bound broker has no CRDR replication, so Promote returns the
// typed BadRequestException — proving the grant without touching
// broker state.
const typed = yield* promote({ Mode: "SWITCHOVER" }).pipe(
Effect.map(() => false),
Effect.catchTag("BadRequestException", () => Effect.succeed(true)),
);
return yield* HttpServerResponse.json({ typed });
}
if (request.method === "POST" && pathname === "/reboot") {
// A real reboot of the RUNNING broker — call this from the LAST
// test; the Broker provider's delete waits for the broker to
// settle before deleting.
yield* rebootBroker();
return yield* HttpServerResponse.json({ rebooting: true });
}
return yield* HttpServerResponse.json(
{ error: "Not found", method: request.method, pathname },
{ status: 404 },
);
}).pipe(Effect.orDie),
};
}).pipe(
Effect.provide(
Layer.mergeAll(
MQ.DescribeBrokerHttp,
MQ.RebootBrokerHttp,
MQ.PromoteHttp,
MQ.CreateUserHttp,
MQ.UpdateUserHttp,
MQ.DeleteUserHttp,
MQ.DescribeUserHttp,
MQ.ListUsersHttp,
MQ.ListBrokersHttp,
),
),
),
);