I am trying to use Pulumi to create an AWS Lambda that manipulates a DynamoDB table and is triggered by an API Gateway HTTP request.
My configuration works perfectly when I run pulumi up
, but when I run Vitest, my test passes but exits with non-zero and this message:
⎯⎯⎯ Unhandled Rejection ⎯⎯⎯
Error: Could not find property info for real property on object: sdk
I can see that the error comes from this code in Pulumi, but I can't figure out what causes it. Am I doing something wrong or is this a bug (in which case I can create an issue)?
Below is a summary that I think has all the relevant info, but there is a minimal repo demonstrating the problem here (GitHub actions fail with the problem I'm describing).
I have an index.ts
file that creates a database, gateway, and lambda:
import * as aws from '@pulumi/aws'
import * as apigateway from '@pulumi/aws-apigateway'
import handler from './handler'
const table = new aws.dynamodb.Table('Table', {...})
const tableAccessPolicy = new aws.iam.Policy('DbAccessPolicy', {
// removed for brevity. Allows put, get, delete
})
const lambdaRole = new aws.iam.Role('lambdaRole', {...})
new aws.iam.RolePolicyAttachment('RolePolicyAttachment', {...})
const callbackFunction = new aws.lambda.CallbackFunction(
'callbackFunction',
{
role: lambdaRole,
callback: handler(table.name),
}
)
const api = new apigateway.RestAPI('api', {
routes: [{
method: 'GET',
path: '/',
eventHandler: callbackFunction,
}]
})
export const dbTable = table
export const url = api.url
The handler
is imported from a separate file:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import * as pulumi from '@pulumi/pulumi';
import * as aws from '@pulumi/aws';
export default function (tableName: pulumi.Output<string>) {
return async function handleDocument(
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> {
try {
const client = new aws.sdk.DynamoDB.DocumentClient();
await client
.put({
TableName: tableName.get(),
Item: { PK: 'hello', roomId: '12345' },
})
.promise();
const result = await client
.get({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
await client
.delete({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
return {
statusCode: 200,
body: JSON.stringify({
item: result.Item,
}),
};
} catch (err) {
return {
statusCode: 200,
body: JSON.stringify({
error: err,
}),
};
}
};
}
Finally, I have a simple test:
import * as pulumi from '@pulumi/pulumi';
import { describe, it, expect, beforeAll } from 'vitest';
pulumi.runtime.setMocks(
{
newResource: function (args: pulumi.runtime.MockResourceArgs): {
id: string;
state: Record<string, any>;
} {
return {
id: `${args.name}_id`,
state: args.inputs,
};
},
call: function (args: pulumi.runtime.MockCallArgs) {
return args.inputs;
},
},
'project',
'stack',
false
);
describe('infrastructure', () => {
let infra: typeof import('./index');
beforeAll(async function () {
// It's important to import the program _after_ the mocks are defined.
infra = await import('./index');
});
it('Creates a DynamoDB table', async () => {
const tableId = await new Promise((resolve) => {
infra?.dbTable?.id.apply((id) => resolve(id));
});
expect(tableId).toBe('Table_id');
});
});