test: Workaround for Jest dynamic import issues

Dynamic imports cause segmentation faults with Jest:
https://github.com/nodejs/node/issues/35889.
We work around this by handling imports in IdentityProviderFactory
differently when Jest is running.
For unit tests we use a different tsconfig
that transpiles dynamic imports differently,
as those are also used in AppRunner.
This commit is contained in:
Joachim Van Herwegen 2023-06-16 10:53:51 +02:00
parent bfa70a40aa
commit cccca96d28
4 changed files with 47 additions and 4 deletions

View File

@ -46,7 +46,7 @@ const esModules = [
module.exports = { module.exports = {
transform: { transform: {
'^.+\\.ts$': [ 'ts-jest', { '^.+\\.ts$': [ 'ts-jest', {
tsconfig: '<rootDir>/tsconfig.json', tsconfig: '<rootDir>/test/tsconfig.json',
diagnostics: false, diagnostics: false,
isolatedModules: true, isolatedModules: true,
}], }],

View File

@ -145,9 +145,23 @@ export class IdentityProviderFactory implements ProviderFactory {
// Render errors with our own error handler // Render errors with our own error handler
this.configureErrors(config); this.configureErrors(config);
// Allow provider to interpret reverse proxy headers.
// As oidc-provider is an ESM package and CSS is CJS, we have to use a dynamic import here. // As oidc-provider is an ESM package and CSS is CJS, we have to use a dynamic import here.
const provider = new (await import('oidc-provider')).default(this.baseUrl, config); // Unfortunately, there is a Node/Jest bug that causes segmentation faults when doing such an import in Jest:
// https://github.com/nodejs/node/issues/35889
// To work around that, we do the import differently, in case we are in a Jest test run.
// This can be detected via the env variables: https://jestjs.io/docs/environment-variables.
// There have been reports of `JEST_WORKER_ID` being undefined, so to be sure we check both.
let ctr: { default: new(issuer: string, configuration?: Configuration) => Provider };
// eslint-disable-next-line no-process-env
if (process.env.JEST_WORKER_ID ?? process.env.NODE_ENV === 'test') {
// eslint-disable-next-line no-undef
ctr = jest.requireActual('oidc-provider');
} else {
ctr = await import('oidc-provider');
}
const provider = new ctr.default(this.baseUrl, config);
// Allow provider to interpret reverse proxy headers.
provider.proxy = true; provider.proxy = true;
this.captureErrorResponses(provider); this.captureErrorResponses(provider);

View File

@ -1,5 +1,8 @@
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": {
"moduleResolution": "node"
},
"include": [ "include": [
"." "."
] ]

View File

@ -37,6 +37,8 @@ const routes = {
}; };
describe('An IdentityProviderFactory', (): void => { describe('An IdentityProviderFactory', (): void => {
let jestWorkerId: string | undefined;
let nodeEnv: string | undefined;
let baseConfig: Configuration; let baseConfig: Configuration;
const baseUrl = 'http://example.com/foo/'; const baseUrl = 'http://example.com/foo/';
const oidcPath = '/oidc'; const oidcPath = '/oidc';
@ -53,8 +55,24 @@ describe('An IdentityProviderFactory', (): void => {
let responseWriter: jest.Mocked<ResponseWriter>; let responseWriter: jest.Mocked<ResponseWriter>;
let factory: IdentityProviderFactory; let factory: IdentityProviderFactory;
beforeAll(async(): Promise<void> => {
// We need to fool the IDP factory into thinking we are not in a test run,
// otherwise we can't mock the oidc-provider library due to the workaround in the code there.
jestWorkerId = process.env.JEST_WORKER_ID;
nodeEnv = process.env.NODE_ENV;
delete process.env.JEST_WORKER_ID;
delete process.env.NODE_ENV;
});
afterAll(async(): Promise<void> => {
process.env.JEST_WORKER_ID = jestWorkerId;
process.env.NODE_ENV = nodeEnv;
});
beforeEach(async(): Promise<void> => { beforeEach(async(): Promise<void> => {
baseConfig = { claims: { webid: [ 'webid', 'client_webid' ]}}; // Disabling devInteractions to prevent warnings when testing the path
// where we use the actual library instead of a mock.
baseConfig = { claims: { webid: [ 'webid', 'client_webid' ]}, features: { devInteractions: { enabled: false }}};
ctx = { ctx = {
method: 'GET', method: 'GET',
@ -308,4 +326,12 @@ describe('An IdentityProviderFactory', (): void => {
expect(oldAccept).toHaveBeenCalledTimes(1); expect(oldAccept).toHaveBeenCalledTimes(1);
expect(oldAccept).toHaveBeenLastCalledWith('something'); expect(oldAccept).toHaveBeenLastCalledWith('something');
}); });
it('avoids dynamic imports when testing with Jest.', async(): Promise<void> => {
// Reset the env variable, so we can test the path where the dynamic import is not used
process.env.JEST_WORKER_ID = jestWorkerId;
const provider = await factory.getProvider() as any;
// We don't define this in our mock
expect(provider.app).toBeDefined();
});
}); });