functionless
functionless copied to clipboard
Design: Post-Deployment Fragment Executable.
Currently our tests follow the pattern:
- define resources and return outputs needed later like arns
- consumes said outputs and make calls via clients bound using test credentials.
- Test the output of api calls
Current Example Test (eventbus.localstack.test.ts):
testResource(
"Bus event starts step function and writes to dynamo",
(parent, role) => {
const addr = new CfnOutput(parent, "out", { value: "" });
const bus = new EventBus<Event<{ id: string }, "test">>(parent, "bus", {
eventBusName: addr.node.addr,
});
const table = Table.fromTable<{ id: string }, "id">(
new aws_dynamodb.Table(parent, "table", {
tableName: addr.node.addr + "table",
partitionKey: {
name: "id",
type: aws_dynamodb.AttributeType.STRING,
},
})
);
const putMachine = new StepFunction<{ id: string }, void>(
parent,
"machine",
async (event) => {
await $AWS.DynamoDB.PutItem({
Item: {
id: { S: event.id },
},
Table: table,
});
}
);
bus.resource.grantPutEventsTo(role);
table.resource.grantReadData(role);
bus
.when(parent, "rule", (event) => event.source === "test")
.map((event) => event.detail)
.pipe(putMachine);
return {
outputs: {
bus: addr.node.addr,
table: addr.node.addr + "table",
},
};
},
async (context, clients) => {
const id = `${context.bus}${Math.floor(Math.random() * 1000000)}`;
await clients.eventBridge
.putEvents({
Entries: [
{
EventBusName: context.bus,
Source: "test",
Detail: JSON.stringify({
id,
}),
DetailType: "someType",
},
],
})
.promise();
// Give time for the event to make it to dynamo. Localstack is pretty slow.
// 1 - 1s
// 2 - 2s
// 3 - 4s
// 4 - 8s
// 5 - 16s
const item = await retry(
() =>
clients.dynamoDB
.getItem({
Key: {
id: { S: id },
},
TableName: context.table,
ConsistentRead: true,
})
.promise(),
(item) => !!item.Item,
5,
10000,
2
);
expect(item.Item).toBeDefined();
}
);
Notice that we need to build the client, grant permissions, and inject the outputs of the stack. This is very similar to how a Functionless Function works already, but the Function uses ENV variables to inject in deployment time values (arns, names, etc).
We should support something like
const bus = new EventBus(...);
const executable = new PostDeploymentExecutable(async () => {
await bus.putEvents(...);
});
executable.execute();
Which would turn the above test into
testResource(
"Bus event starts step function and writes to dynamo",
(parent, role) => {
const addr = new CfnOutput(parent, "out", { value: "" });
const bus = new EventBus<Event<{ id: string }, "test">>(parent, "bus", {
eventBusName: addr.node.addr,
});
const table = Table.fromTable<{ id: string }, "id">(
new aws_dynamodb.Table(parent, "table", {
tableName: addr.node.addr + "table",
partitionKey: {
name: "id",
type: aws_dynamodb.AttributeType.STRING,
},
})
);
const putMachine = new StepFunction<{ id: string }, void>(
parent,
"machine",
async (event) => {
await $AWS.DynamoDB.PutItem({
Item: {
id: { S: event.id },
},
Table: table,
});
}
);
bus
.when(parent, "rule", (event) => event.source === "test")
.map((event) => event.detail)
.pipe(putMachine);
// executed by the test framework after programmatic deployment
return new PostDeployExecutable(async () => {
const id = `${bus.eventBusName}${Math.floor(Math.random() * 1000000)}`;
await bus.putEvents({
Source: "test",
Detail: JSON.stringify({
id,
}),
DetailType: "someType",
});
const item = await retry(
() =>
table
.getItem({
Key: {
id: { S: id },
},
ConsistentRead: true,
}),
(item) => !!item.Item,
5,
10000,
2
);
expect(item.Item).toBeDefined();
});
}
);