t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/AWS/AppSync/DataSource.test.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

127 lines
4.2 KiB
TypeScript

import * as AWS from "@/AWS";
import * as Test from "@/Test/Alchemy";
import * as appsync from "@distilled.cloud/aws/appsync";
import * as iam from "@distilled.cloud/aws/iam";
import { expect } from "alchemy-test";
import * as Effect from "effect/Effect";
const { test } = Test.make({ providers: AWS.providers() });
const SCHEMA = `
type Query {
hello: String
}
schema { query: Query }
`;
test.provider(
"NONE and DynamoDB data sources (auto-created service role)",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const deploy = (description: string) =>
stack.deploy(
Effect.gen(function* () {
const api = yield* AWS.AppSync.GraphqlApi("DSApi", {
schema: SCHEMA,
});
const none = yield* AWS.AppSync.DataSource("LocalDS", {
api,
type: "NONE",
description,
});
const table = yield* AWS.DynamoDB.Table("DSTable", {
partitionKey: "pk",
attributes: { pk: "S" },
billingMode: "PAY_PER_REQUEST",
});
const ddb = yield* AWS.AppSync.DataSource("TableDS", {
api,
type: "AMAZON_DYNAMODB",
dynamodbConfig: { tableName: table.tableName },
});
return {
apiId: api.apiId,
noneName: none.name,
noneDescription: description,
ddbName: ddb.name,
ddbRoleName: ddb.roleName,
ddbServiceRoleArn: ddb.serviceRoleArn,
tableName: table.tableName,
tableArn: table.tableArn,
};
}),
);
const out = yield* deploy("v1");
// Data source names satisfy AppSync's [_A-Za-z][_0-9A-Za-z]* rule.
expect(out.noneName).toMatch(/^[_A-Za-z][_0-9A-Za-z]*$/);
expect(out.ddbName).toMatch(/^[_A-Za-z][_0-9A-Za-z]*$/);
// Out-of-band verification via distilled.
const none = yield* appsync.getDataSource({
apiId: out.apiId,
name: out.noneName,
});
expect(none.dataSource?.type).toBe("NONE");
expect(none.dataSource?.description).toBe("v1");
const ddb = yield* appsync.getDataSource({
apiId: out.apiId,
name: out.ddbName,
});
expect(ddb.dataSource?.type).toBe("AMAZON_DYNAMODB");
expect(ddb.dataSource?.dynamodbConfig?.tableName).toBe(out.tableName);
expect(ddb.dataSource?.serviceRoleArn).toBe(out.ddbServiceRoleArn);
// The auto-created role trusts appsync.amazonaws.com and grants
// least-privilege access to the table.
expect(out.ddbRoleName).toBeTruthy();
const role = yield* iam.getRole({ RoleName: out.ddbRoleName! });
expect(
decodeURIComponent(role.Role.AssumeRolePolicyDocument ?? ""),
).toContain("appsync.amazonaws.com");
const policy = yield* iam.getRolePolicy({
RoleName: out.ddbRoleName!,
PolicyName: `${out.ddbRoleName}-policy`,
});
const policyDocument = decodeURIComponent(policy.PolicyDocument);
expect(policyDocument).toContain(out.tableArn);
expect(policyDocument).toContain("dynamodb:Query");
// Update the NONE data source description in place (same name).
const updated = yield* deploy("v2");
expect(updated.noneName).toBe(out.noneName);
const afterUpdate = yield* appsync.getDataSource({
apiId: out.apiId,
name: out.noneName,
});
expect(afterUpdate.dataSource?.description).toBe("v2");
yield* stack.destroy();
// Both the data source's API and the managed role are gone.
const roleGone = yield* iam.getRole({ RoleName: out.ddbRoleName! }).pipe(
Effect.map(() => false),
Effect.catchTag("NoSuchEntityException", () => Effect.succeed(true)),
);
expect(roleGone).toBe(true);
const apiGone = yield* appsync.getGraphqlApi({ apiId: out.apiId }).pipe(
Effect.map(() => false),
Effect.catchTag("NotFoundException", () => Effect.succeed(true)),
);
expect(apiGone).toBe(true);
}),
{
tags: [
"provider:aws",
"provider:aws:appsync",
"provider:aws:dynamodb",
"provider:aws:iam",
"live",
],
timeout: 240_000,
},
);