Skip to content

Commit 39d9173

Browse files
committed
Add unit tests for config helpers
1 parent 4bd2507 commit 39d9173

File tree

4 files changed

+217
-6
lines changed

4 files changed

+217
-6
lines changed

packages/solidstart/src/config/addInstrumentation.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import * as path from 'path';
33
import { consoleSandbox } from '@sentry/utils';
44
import type { Nitro } from './types';
55

6+
// Nitro presets for hosts that only host static files
7+
export const staticHostPresets = ['github_pages'];
8+
// Nitro presets for hosts that use `server.mjs` as opposed to `index.mjs`
9+
export const serverFilePresets = ['netlify'];
10+
611
/**
712
* Adds the built `instrument.server.js` file to the output directory.
813
*
@@ -11,13 +16,17 @@ import type { Nitro } from './types';
1116
* added to `app.config.ts` to enable building the instrumentation file.
1217
*/
1318
export async function addInstrumentationFileToBuild(nitro: Nitro): Promise<void> {
19+
// Static file hosts have no server component so there's nothing to do
20+
if (staticHostPresets.includes(nitro.options.preset)) {
21+
return;
22+
}
23+
1424
const buildDir = nitro.options.buildDir;
1525
const serverDir = nitro.options.output.serverDir;
1626
const source = path.resolve(buildDir, 'build', 'ssr', 'instrument.server.js');
1727
const destination = path.resolve(serverDir, 'instrument.server.mjs');
1828

1929
try {
20-
await fs.promises.access(source, fs.constants.F_OK);
2130
await fs.promises.copyFile(source, destination);
2231

2332
consoleSandbox(() => {
@@ -43,10 +52,13 @@ export async function experimental_addInstrumentationFileTopLevelImportToServerE
4352
serverDir: string,
4453
preset: string,
4554
): Promise<void> {
46-
// other presets ('node-server' or 'vercel') have an index.mjs
47-
const presetsWithServerFile = ['netlify'];
55+
// Static file hosts have no server component so there's nothing to do
56+
if (staticHostPresets.includes(preset)) {
57+
return;
58+
}
59+
4860
const instrumentationFile = path.resolve(serverDir, 'instrument.server.mjs');
49-
const serverEntryFileName = presetsWithServerFile.includes(preset) ? 'server.mjs' : 'index.mjs';
61+
const serverEntryFileName = serverFilePresets.includes(preset) ? 'server.mjs' : 'index.mjs';
5062
const serverEntryFile = path.resolve(serverDir, serverEntryFileName);
5163

5264
try {
@@ -55,7 +67,7 @@ export async function experimental_addInstrumentationFileTopLevelImportToServerE
5567
consoleSandbox(() => {
5668
// eslint-disable-next-line no-console
5769
console.warn(
58-
`[Sentry SolidStart withSentry] Tried to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`,
70+
`[Sentry SolidStart withSentry] Failed to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`,
5971
error,
6072
);
6173
});

packages/solidstart/src/config/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export type Nitro = {
55
options: {
66
buildDir: string;
77
output: {
8-
buildDir: string;
98
serverDir: string;
109
};
1110
preset: string;
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import type { Nitro } from '../../build/types/config/types';
3+
import {
4+
addInstrumentationFileToBuild,
5+
experimental_addInstrumentationFileTopLevelImportToServerEntry,
6+
serverFilePresets,
7+
staticHostPresets,
8+
} from '../../src/config/addInstrumentation';
9+
10+
const consoleLogSpy = vi.spyOn(console, 'log');
11+
const consoleWarnSpy = vi.spyOn(console, 'warn');
12+
const fsAccessMock = vi.fn();
13+
const fsCopyFileMock = vi.fn();
14+
const fsReadFile = vi.fn();
15+
const fsWriteFileMock = vi.fn();
16+
17+
vi.mock('fs', async () => {
18+
const actual = await vi.importActual('fs');
19+
return {
20+
...actual,
21+
promises: {
22+
// @ts-expect-error this exists
23+
...actual.promises,
24+
access: (...args: unknown[]) => fsAccessMock(...args),
25+
copyFile: (...args: unknown[]) => fsCopyFileMock(...args),
26+
readFile: (...args: unknown[]) => fsReadFile(...args),
27+
writeFile: (...args: unknown[]) => fsWriteFileMock(...args),
28+
},
29+
};
30+
});
31+
32+
beforeEach(() => {
33+
vi.clearAllMocks();
34+
});
35+
36+
describe('addInstrumentationFileToBuild()', () => {
37+
const nitroOptions: Nitro = {
38+
options: {
39+
buildDir: '/path/to/buildDir',
40+
output: {
41+
serverDir: '/path/to/serverDir',
42+
},
43+
preset: 'vercel',
44+
},
45+
};
46+
47+
it('adds `instrument.server.mjs` to the server output directory', async () => {
48+
fsCopyFileMock.mockResolvedValueOnce(true);
49+
await addInstrumentationFileToBuild(nitroOptions);
50+
expect(fsCopyFileMock).toHaveBeenCalledWith(
51+
'/path/to/buildDir/build/ssr/instrument.server.js',
52+
'/path/to/serverDir/instrument.server.mjs',
53+
);
54+
expect(consoleLogSpy).toHaveBeenCalledWith(
55+
'[Sentry SolidStart withSentry] Successfully created /path/to/serverDir/instrument.server.mjs.',
56+
);
57+
});
58+
59+
it('warns when `instrument.server.js` can not be copied to the server output directory', async () => {
60+
const error = new Error('Failed to copy file.');
61+
fsCopyFileMock.mockRejectedValueOnce(error);
62+
await addInstrumentationFileToBuild(nitroOptions);
63+
expect(fsCopyFileMock).toHaveBeenCalledWith(
64+
'/path/to/buildDir/build/ssr/instrument.server.js',
65+
'/path/to/serverDir/instrument.server.mjs',
66+
);
67+
expect(consoleWarnSpy).toHaveBeenCalledWith(
68+
'[Sentry SolidStart withSentry] Failed to create /path/to/serverDir/instrument.server.mjs.',
69+
error,
70+
);
71+
});
72+
73+
it.each([staticHostPresets])("doesn't add `instrument.server.mjs` for static host `%s`", async preset => {
74+
await addInstrumentationFileToBuild({
75+
...nitroOptions,
76+
options: {
77+
...nitroOptions.options,
78+
preset,
79+
},
80+
});
81+
expect(fsCopyFileMock).not.toHaveBeenCalled();
82+
});
83+
});
84+
85+
describe('experimental_addInstrumentationFileTopLevelImportToServerEntry()', () => {
86+
it('adds a top level import of `instrument.server.mjs` to the index.mjs entry file', async () => {
87+
fsAccessMock.mockResolvedValueOnce(true);
88+
fsReadFile.mockResolvedValueOnce("import process from 'node:process';");
89+
fsWriteFileMock.mockResolvedValueOnce(true);
90+
await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', 'node_server');
91+
expect(fsWriteFileMock).toHaveBeenCalledWith(
92+
'/path/to/serverDir/index.mjs',
93+
"import './instrument.server.mjs';\nimport process from 'node:process';",
94+
);
95+
expect(consoleLogSpy).toHaveBeenCalledWith(
96+
'[Sentry SolidStart withSentry] Added `/path/to/serverDir/instrument.server.mjs` as top level import to `/path/to/serverDir/index.mjs`.',
97+
);
98+
});
99+
100+
it.each([serverFilePresets])(
101+
'adds a top level import of `instrument.server.mjs` to the server.mjs entry file for preset `%s`',
102+
async preset => {
103+
fsAccessMock.mockResolvedValueOnce(true);
104+
fsReadFile.mockResolvedValueOnce("import process from 'node:process';");
105+
fsWriteFileMock.mockResolvedValueOnce(true);
106+
await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', preset);
107+
expect(fsWriteFileMock).toHaveBeenCalledWith(
108+
'/path/to/serverDir/server.mjs',
109+
"import './instrument.server.mjs';\nimport process from 'node:process';",
110+
);
111+
expect(consoleLogSpy).toHaveBeenCalledWith(
112+
'[Sentry SolidStart withSentry] Added `/path/to/serverDir/instrument.server.mjs` as top level import to `/path/to/serverDir/server.mjs`.',
113+
);
114+
},
115+
);
116+
117+
it("doesn't modify the sever entry file if `instrumentation.server.mjs` is not found", async () => {
118+
const error = new Error('File not found.');
119+
fsAccessMock.mockRejectedValueOnce(error);
120+
await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', 'node_server');
121+
expect(consoleWarnSpy).toHaveBeenCalledWith(
122+
'[Sentry SolidStart withSentry] Failed to add `/path/to/serverDir/instrument.server.mjs` as top level import to `/path/to/serverDir/index.mjs`.',
123+
error,
124+
);
125+
});
126+
127+
it.each([staticHostPresets])(
128+
"doesn't import `instrument.server.mjs` as top level import for host `%s`",
129+
async preset => {
130+
fsAccessMock.mockResolvedValueOnce(true);
131+
await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', preset);
132+
expect(fsWriteFileMock).not.toHaveBeenCalled();
133+
},
134+
);
135+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { beforeEach, describe, it, vi } from 'vitest';
2+
import type { Nitro } from '../../build/types/config/types';
3+
import { withSentry } from '../../src/config';
4+
5+
const userDefinedNitroRollupBeforeHookMock = vi.fn();
6+
const userDefinedNitroCloseHookMock = vi.fn();
7+
const addInstrumentationFileToBuildMock = vi.fn();
8+
const experimental_addInstrumentationFileTopLevelImportToServerEntryMock = vi.fn();
9+
10+
vi.mock('../../src/config/addInstrumentation', () => ({
11+
addInstrumentationFileToBuild: (...args: unknown[]) => addInstrumentationFileToBuildMock(...args),
12+
experimental_addInstrumentationFileTopLevelImportToServerEntry: (...args: unknown[]) =>
13+
experimental_addInstrumentationFileTopLevelImportToServerEntryMock(...args),
14+
}));
15+
16+
beforeEach(() => {
17+
vi.clearAllMocks();
18+
});
19+
20+
describe('withSentry()', () => {
21+
const solidStartConfig = {
22+
middleware: './src/middleware.ts',
23+
server: {
24+
hooks: {
25+
close: userDefinedNitroCloseHookMock,
26+
'rollup:before': userDefinedNitroRollupBeforeHookMock,
27+
},
28+
},
29+
};
30+
const nitroOptions: Nitro = {
31+
options: {
32+
buildDir: '/path/to/buildDir',
33+
output: {
34+
serverDir: '/path/to/serverDir',
35+
},
36+
preset: 'vercel',
37+
},
38+
};
39+
40+
it('adds a nitro hook to add the instrumentation file to the build', async () => {
41+
const config = withSentry(solidStartConfig);
42+
await config?.server.hooks['rollup:before'](nitroOptions);
43+
expect(addInstrumentationFileToBuildMock).toHaveBeenCalledWith(nitroOptions);
44+
expect(userDefinedNitroRollupBeforeHookMock).toHaveBeenCalledWith(nitroOptions);
45+
});
46+
47+
it('adds a nitro hook to add the instrumentation file as top level import to the server entry file', async () => {
48+
const config = withSentry(solidStartConfig, { experimental_basicServerTracing: true });
49+
await config?.server.hooks['rollup:before'](nitroOptions);
50+
await config?.server.hooks['close'](nitroOptions);
51+
expect(experimental_addInstrumentationFileTopLevelImportToServerEntryMock).toHaveBeenCalledWith(
52+
'/path/to/serverDir',
53+
'vercel',
54+
);
55+
expect(userDefinedNitroCloseHookMock).toHaveBeenCalled();
56+
});
57+
58+
it('does not add the instrumentation file as top level import if experimental flag was not true', async () => {
59+
const config = withSentry(solidStartConfig, { experimental_basicServerTracing: false });
60+
await config?.server.hooks['rollup:before'](nitroOptions);
61+
await config?.server.hooks['close'](nitroOptions);
62+
expect(experimental_addInstrumentationFileTopLevelImportToServerEntryMock).not.toHaveBeenCalled();
63+
expect(userDefinedNitroCloseHookMock).toHaveBeenCalled();
64+
});
65+
});

0 commit comments

Comments
 (0)