Skip to content

Add module loading tests for user function #124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ _Description of changes:_

_Target (OCI, Managed Runtime, both):_

## Checklist
- [ ] I have run `npm install` to generate the `package-lock.json` correctly and included it in the PR.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ dist/

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

deps/artifacts/
deps/aws-lambda-cpp*/
deps/curl*/
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ Then,
* to run integration tests: `make test-integ`
* to run smoke tests: `make test-smoke`

### Raising a PR
When modifying dependencies (`package.json`), make sure to:
1. Run `npm install` to generate an updated `package-lock.json`
2. Commit both `package.json` and `package-lock.json` together

We require package-lock.json to be checked in to ensure consistent installations across development environments.

### Troubleshooting

While running integration tests, you might encounter the Docker Hub rate limit error with the following body:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions test/handlers/extensionless/esm-extensionless
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

// This should fail because it's ESM syntax in a CJS context
export const handler = async (event) => {
return "This should fail";
};
8 changes: 8 additions & 0 deletions test/handlers/extensionless/index
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';
// This is a CommonJS module without file extension

module.exports.handler = async (event) => {
return "Hello from extensionless CJS";
};
9 changes: 9 additions & 0 deletions test/handlers/pkg-less/cjsAndMjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

import { someESMFunction } from './esmModule.js'; // ESM import

module.exports.handler = async (event) => { // CJS export
return someESMFunction(event);
};

export const esm = 'This is ESM syntax'; // ESM export
9 changes: 9 additions & 0 deletions test/handlers/pkg-less/cjsImportCjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';

const { getMessage } = require('./cjsModule.cjs')

exports.handler = async (_event) => {
return getMessage();
}
10 changes: 10 additions & 0 deletions test/handlers/pkg-less/cjsImportESM.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';

// This static import is not allowed in CJS
import { getMessage } from './esmModule';

module.exports.handler = async () => {
return getMessage();
};
8 changes: 8 additions & 0 deletions test/handlers/pkg-less/cjsInMjs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';

// This should fail because it's CJS syntax in a ESM context
module.exports.handler = async (_event) => {
return 'This should fail';
};
7 changes: 7 additions & 0 deletions test/handlers/pkg-less/cjsModule.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';

module.exports.getMessage = () => {
return "Hello from CJS!";
};
7 changes: 7 additions & 0 deletions test/handlers/pkg-less/esmImportCjs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

import { getMessage } from './cjsModule.cjs';

export const handler = async (_event) => {
return getMessage();
};
6 changes: 6 additions & 0 deletions test/handlers/pkg-less/esmInCjs.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

// This should fail because it's ESM syntax in a CJS context
export const handler = async (_event) => {
return 'This should fail';
};
9 changes: 9 additions & 0 deletions test/handlers/pkg-less/esmModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

export const handler = async (_event) => {
return 'Hello from ESM.js';
};

export const getMessage = () => {
return "Hello from ESM!";
};
7 changes: 7 additions & 0 deletions test/handlers/pkg-less/esmRequireCjs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

const { getMessage } = require('./cjsModule.cjs')

export const handler = async (_event) => {
return getMessage();
};
8 changes: 8 additions & 0 deletions test/handlers/pkg/type-cjs/cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';
// This is a CommonJS module without file extension

module.exports.handler = async (event) => {
return "Hello from extensionless CJS";
};
7 changes: 7 additions & 0 deletions test/handlers/pkg/type-cjs/cjsModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';

module.exports.handler = async (_event) => {
return 'Hello from CJS.js';
};
6 changes: 6 additions & 0 deletions test/handlers/pkg/type-cjs/esm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

// This should fail because it's ESM syntax in a CJS context
export const handler = async (event) => {
return "This should fail";
};
6 changes: 6 additions & 0 deletions test/handlers/pkg/type-cjs/esmModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

// This should fail because it's ESM syntax in a CJS context
export const handler = async (_event) => {
return 'This should fail';
};
3 changes: 3 additions & 0 deletions test/handlers/pkg/type-cjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
8 changes: 8 additions & 0 deletions test/handlers/pkg/type-esm/cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';
// This is a CommonJS module without file extension

module.exports.handler = async (event) => {
return "Hello from extensionless CJS";
};
8 changes: 8 additions & 0 deletions test/handlers/pkg/type-esm/cjsModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

'use strict';

// This should fail because it's CJS syntax in a ESM context
module.exports.handler = async (_event) => {
return 'This should fail';
};
6 changes: 6 additions & 0 deletions test/handlers/pkg/type-esm/esm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

// This should fail because it's ESM syntax in a CJS context
export const handler = async (event) => {
return "This should fail";
};
5 changes: 5 additions & 0 deletions test/handlers/pkg/type-esm/esmModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

export const handler = async (_event) => {
return 'Hello from ESM.js';
};
3 changes: 3 additions & 0 deletions test/handlers/pkg/type-esm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
161 changes: 161 additions & 0 deletions test/unit/UserFunctionTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
HandlerNotFound,
ImportModuleError,
MalformedHandlerName,
UserCodeSyntaxError,
} = require('lambda-runtime/Errors.js');
const UserFunction = require('lambda-runtime/UserFunction.js');

Expand Down Expand Up @@ -250,6 +251,166 @@ describe('UserFunction.load method', () => {

response.should.be.resolvedWith('moon');
});

it('should successfully load a CJS handler from extensionless file (no package.json)', async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

const handler = await UserFunction.load(
path.join(HANDLERS_ROOT, 'extensionless'),
'index.handler',
);
const response = await handler('test event');

response.should.equal('Hello from extensionless CJS');
});

it('should fail to load ESM syntax from extensionless file (no package.json)', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'extensionless'),
'esm-extensionless.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should load CJS handler from extensionless file with type:commonjs', async () => {
// package.json is ignored in the case of extensionless
const handler = await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'),
'cjs.handler',
);
const response = await handler('test event');

response.should.equal('Hello from extensionless CJS');
});

it('should fail to load ESM handler from extensionless file with type:commonjs', async () => {
// package.json is ignored in the case of extensionless
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'),
'esm.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should load CJS handler from extensionless file with type:module', async () => {
// package.json is ignored in the case of extensionless
const handler = await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-esm'),
'cjs.handler',
);
const response = await handler('test event');

response.should.equal('Hello from extensionless CJS');
});

it('should fail to load ESM handler from extensionless file with type:module', async () => {
// package.json is ignored in the case of extensionless
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-esm'),
'esm.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should load CJS handler from JS file with type:commonjs', async () => {
const handler = await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'),
'cjsModule.handler',
);
const response = await handler('test event');

response.should.equal('Hello from CJS.js');
});

it('should fail to load ESM handler from JS file with type:commonjs', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-cjs'),
'esmModule.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should load ESM handler from JS file with type:module', async () => {
const handler = await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-esm'),
'esmModule.handler',
);
const response = await handler('test event');

response.should.equal('Hello from ESM.js');
});

it('should fail to load CJS handler from JS file with type:module', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg', 'type-esm'),
'cjsModule.handler',
).should.be.rejectedWith(
ReferenceError,
/module is not defined in ES module scope/,
);
});

it('should fail to load ESM handler from JS file without type context', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'esmModule.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should fail to load CJS handler from MJS file without type context', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'cjsInMjs.handler',
).should.be.rejectedWith(
ReferenceError,
/module is not defined in ES module scope/,
);
});

it('should fail to load ESM handler from CJS file without type context', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'esmInCjs.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should fail to load mixed context handler from JS file without type context', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'cjsAndMjs.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should successfully load ESM handler importing from CJS', async () => {
const handler = await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'esmImportCjs.handler',
);

const response = await handler();
response.should.equal('Hello from CJS!');
});

it('should fail when CJS tries to import from ESM using static import', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'cjsImportESM.handler',
).should.be.rejectedWith(UserCodeSyntaxError);
});

it('should successfully load CJS handler importing from CJS', async () => {
const handler = await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'cjsImportCjs.handler',
);

const response = await handler();
response.should.equal('Hello from CJS!');
});

it('should fail when using require in .mjs', async () => {
await UserFunction.load(
path.join(HANDLERS_ROOT, 'pkg-less'),
'esmRequireCjs.handler',
).should.be.rejectedWith(
ReferenceError,
/require is not defined in ES module scope/,
);
});
});

describe('type guards HandlerFunction', () => {
Expand Down