Skip to content

Commit 11693c8

Browse files
MarcLLukas Holzer
and
Lukas Holzer
authored
test: cache unit tests (#37)
* test: tests for cache --------- Co-authored-by: Lukas Holzer <[email protected]>
1 parent a458468 commit 11693c8

14 files changed

+351
-52
lines changed

package-lock.json

+209-35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,12 @@
5050
"@netlify/eslint-config-node": "^7.0.1",
5151
"@netlify/serverless-functions-api": "^1.10.1",
5252
"@netlify/zip-it-and-ship-it": "^9.25.5",
53-
"@types/mock-fs": "^4.13.1",
5453
"@types/uuid": "^9.0.6",
5554
"cheerio": "^1.0.0-rc.12",
5655
"execa": "^8.0.1",
5756
"get-port": "^7.0.0",
5857
"lambda-local": "^2.1.2",
59-
"mock-fs": "^5.2.0",
58+
"memfs": "^4.6.0",
6059
"next": "^13.5.4",
6160
"typescript": "^5.1.6",
6261
"uuid": "^9.0.1",

src/build/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const PLUGIN_DIR = resolve(`${MODULE_DIR}../..`)
66
export const WORKING_DIR = process.cwd()
77

88
export const BUILD_DIR = `${WORKING_DIR}/.netlify`
9+
export const REL_BUILD_DIR = '.netlify'
910

1011
export const SERVER_FUNCTIONS_DIR = `${BUILD_DIR}/functions-internal`
1112
export const SERVER_HANDLER_NAME = '___netlify-server-handler'

src/build/content/prerendered.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { join } from 'node:path'
66
import { cpus } from 'os'
77
import pLimit from 'p-limit'
88
import { parse, ParsedPath } from 'path'
9-
import { BUILD_DIR } from '../constants.js'
9+
import { REL_BUILD_DIR } from '../constants.js'
1010

1111
export type CacheEntry = {
1212
key: string
@@ -145,7 +145,7 @@ export const uploadPrerenderedContent = async ({
145145

146146
// read prerendered content and build JSON key/values for the blob store
147147
const entries = await Promise.allSettled(
148-
await buildPrerenderedContentEntries(`${BUILD_DIR}/.next`),
148+
await buildPrerenderedContentEntries(join(process.cwd(), REL_BUILD_DIR, '.next')),
149149
)
150150
entries.forEach((result) => {
151151
if (result.status === 'rejected') {

src/build/content/static.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { globby } from 'globby'
33
import { existsSync } from 'node:fs'
44
import { copyFile, cp, mkdir } from 'node:fs/promises'
55
import { ParsedPath, dirname, join, parse } from 'node:path'
6-
import { BUILD_DIR, WORKING_DIR } from '../constants.js'
6+
import { REL_BUILD_DIR, WORKING_DIR } from '../constants.js'
77

88
/**
99
* Copy static pages (HTML without associated JSON data)
@@ -36,7 +36,7 @@ const copyStaticAssets = async ({
3636
PUBLISH_DIR,
3737
}: Pick<NetlifyPluginConstants, 'PUBLISH_DIR'>): Promise<void> => {
3838
try {
39-
const src = join(BUILD_DIR, '.next/static')
39+
const src = join(process.cwd(), REL_BUILD_DIR, '.next/static')
4040
const dist = join(PUBLISH_DIR, '_next/static')
4141
await mkdir(dist, { recursive: true })
4242
cp(src, dist, { recursive: true, force: true })
@@ -66,7 +66,7 @@ const copyPublicAssets = async ({
6666
*/
6767
export const copyStaticContent = async ({ PUBLISH_DIR }: NetlifyPluginConstants): Promise<void> => {
6868
await Promise.all([
69-
copyStaticPages(`${BUILD_DIR}/.next`, PUBLISH_DIR),
69+
copyStaticPages(join(process.cwd(), REL_BUILD_DIR, '.next'), PUBLISH_DIR),
7070
copyStaticAssets({ PUBLISH_DIR }),
7171
copyPublicAssets({ PUBLISH_DIR }),
7272
])

src/build/functions/server.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { nodeFileTrace } from '@vercel/nft'
22
import { writeFile, rm, mkdir, cp } from 'fs/promises'
3-
import { BUILD_DIR, PLUGIN_DIR, SERVER_HANDLER_DIR, SERVER_HANDLER_NAME } from '../constants.js'
3+
import { REL_BUILD_DIR, PLUGIN_DIR, SERVER_HANDLER_DIR, SERVER_HANDLER_NAME } from '../constants.js'
44
import { copyServerContent } from '../content/server.js'
55
import { join } from 'node:path'
66
import { readFileSync } from 'fs'
@@ -33,11 +33,11 @@ export const createServerHandler = async () => {
3333

3434
// copy the next.js standalone build output to the handler directory
3535
await copyServerContent(
36-
join(BUILD_DIR, '.next/standalone/.next'),
36+
join(process.cwd(), REL_BUILD_DIR, '.next/standalone/.next'),
3737
join(SERVER_HANDLER_DIR, '.next'),
3838
)
3939
await cp(
40-
join(BUILD_DIR, '.next/standalone/node_modules'),
40+
join(process.cwd(), REL_BUILD_DIR, '.next/standalone/node_modules'),
4141
join(SERVER_HANDLER_DIR, 'node_modules'),
4242
{ recursive: true },
4343
)

src/build/move-build-output.test.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { fs, vol } from 'memfs'
2+
import { Mock, TestContext, afterEach, assert, beforeEach, expect, test, vi } from 'vitest'
3+
4+
import { NetlifyPluginUtils } from '@netlify/build'
5+
import { join } from 'node:path'
6+
import { mockFileSystem } from '../../tests/index.js'
7+
import { moveBuildOutput } from './move-build-output.js'
8+
9+
vi.mock('node:fs', () => fs)
10+
vi.mock('node:fs/promises', () => fs.promises)
11+
12+
type Context = TestContext & {
13+
utils: {
14+
build: {
15+
failBuild: Mock<any, any>
16+
}
17+
}
18+
}
19+
20+
beforeEach<Context>((ctx) => {
21+
ctx.utils = {
22+
build: {
23+
failBuild: vi.fn().mockImplementation((msg, { error } = {}) => {
24+
assert.fail(`${msg}: ${error || ''}`)
25+
}),
26+
},
27+
}
28+
})
29+
30+
afterEach(() => {
31+
vol.reset()
32+
vi.restoreAllMocks()
33+
})
34+
35+
test<Context>('should fail the build and throw an error if the PUBLISH_DIR does not exist', async (ctx) => {
36+
const cwd = mockFileSystem({})
37+
ctx.utils.build.failBuild.mockImplementation((msg) => {
38+
throw new Error(msg)
39+
})
40+
41+
try {
42+
await moveBuildOutput(
43+
{ PUBLISH_DIR: join(cwd, 'does-not-exist') },
44+
ctx.utils as unknown as NetlifyPluginUtils,
45+
)
46+
} catch (err) {
47+
expect(err).toBeInstanceOf(Error)
48+
if (err instanceof Error) {
49+
expect(err.message).toEqual(
50+
`Your publish directory does not exist. Please check your netlify.toml file.`,
51+
)
52+
}
53+
expect(ctx.utils.build.failBuild).toHaveBeenCalledTimes(1)
54+
} finally {
55+
expect.assertions(3)
56+
}
57+
})
58+
59+
test<Context>('should move the build output to the `.netlify/.next` folder', async (ctx) => {
60+
const cwd = mockFileSystem({
61+
'out/fake-file.js': '',
62+
})
63+
64+
await moveBuildOutput(
65+
{ PUBLISH_DIR: join(cwd, 'out') },
66+
ctx.utils as unknown as NetlifyPluginUtils,
67+
)
68+
69+
expect(vol.toJSON()).toEqual({
70+
[join(cwd, '.netlify/.next/fake-file.js')]: '',
71+
[join(cwd, 'out')]: null,
72+
})
73+
})

src/build/cache.ts renamed to src/build/move-build-output.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { NetlifyPluginConstants, NetlifyPluginUtils } from '@netlify/build'
22
import { existsSync } from 'node:fs'
33
import { mkdir, rename, rm } from 'node:fs/promises'
44
import { join } from 'node:path'
5-
import { BUILD_DIR } from './constants.js'
5+
import { REL_BUILD_DIR } from './constants.js'
66

77
/**
88
* Move the Next.js build output from the publish dir to a temp dir
99
*/
1010
export const moveBuildOutput = async (
11-
{ PUBLISH_DIR }: NetlifyPluginConstants,
11+
{ PUBLISH_DIR }: Pick<NetlifyPluginConstants, 'PUBLISH_DIR'>,
1212
utils: NetlifyPluginUtils,
1313
): Promise<void> => {
1414
if (!existsSync(PUBLISH_DIR)) {
@@ -17,7 +17,7 @@ export const moveBuildOutput = async (
1717
)
1818
}
1919

20-
const tempDir = join(BUILD_DIR, '.next')
20+
const tempDir = join(process.cwd(), REL_BUILD_DIR, '.next')
2121

2222
try {
2323
// cleanup any existing directory

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NetlifyPluginOptions } from '@netlify/build'
2-
import { moveBuildOutput } from './build/cache.js'
2+
import { moveBuildOutput } from './build/move-build-output.js'
33
import { setBuildConfig } from './build/config.js'
44
import { uploadPrerenderedContent } from './build/content/prerendered.js'
55
import { copyStaticContent } from './build/content/static.js'

tests/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './utils/index.js'

tests/utils/helpers.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import getPort from 'get-port'
2-
import { BLOB_TOKEN, type FixtureTestContext } from './fixture'
2+
import { BLOB_TOKEN, type FixtureTestContext } from './fixture.js'
33

44
import { BlobsServer, getDeployStore } from '@netlify/blobs'
5+
import type { NetlifyPluginUtils } from '@netlify/build'
56
import { mkdtemp } from 'node:fs/promises'
67
import { tmpdir } from 'node:os'
78
import { join } from 'node:path'
9+
import { assert } from 'vitest'
810

911
/**
1012
* Generates a 24char deploy ID (this is validated in the blob storage so we cant use a uuidv4)
@@ -63,3 +65,18 @@ export const getBlobEntries = async (ctx: FixtureTestContext) => {
6365
const { blobs } = await ctx.blobStore.list()
6466
return blobs
6567
}
68+
69+
/**
70+
* Fake build utils that are passed to a build plugin execution
71+
*/
72+
export const mockBuildUtils = {
73+
failBuild: (message: string, options: { error?: Error }) => {
74+
assert.fail(`${message}: ${options?.error || ''}`)
75+
},
76+
failPlugin: (message: string, options: { error?: Error }) => {
77+
assert.fail(`${message}: ${options?.error || ''}`)
78+
},
79+
cancelBuild: (message: string, options: { error?: Error }) => {
80+
assert.fail(`${message}: ${options?.error || ''}`)
81+
},
82+
} as unknown as NetlifyPluginUtils

tests/utils/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './helpers.js'
2+
export * from './mock-file-system.js'
3+
export * from './stream-to-string.js'

tests/utils/mock-file-system.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import fs from 'node:fs'
2+
import { platform } from 'node:os'
3+
import { join } from 'node:path'
4+
5+
import { vol } from 'memfs'
6+
import { vi } from 'vitest'
7+
8+
export const osHomeDir = platform() === 'win32' ? 'C:\\Users\\test-user' : '/home/test-user'
9+
10+
/**
11+
* Creates a mocked file system
12+
* @param fileSystem The object of the files
13+
* @param folder Optional folder where it should be placed inside the osHomeDir
14+
* @returns The cwd where all files are placed in
15+
*/
16+
export const mockFileSystem = (fileSystem: Record<string, string>, folder = 'test') => {
17+
vol.fromJSON(
18+
Object.entries(fileSystem).reduce(
19+
(prev, [key, value]) => ({
20+
...prev,
21+
[folder ? join(osHomeDir, folder, key) : key]: value,
22+
}),
23+
{},
24+
),
25+
)
26+
27+
const cwd = folder ? join(osHomeDir, folder) : process.cwd()
28+
vi.spyOn(process, 'cwd').mockReturnValue(cwd)
29+
return cwd
30+
}

tsconfig.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"target": "es2021",
44
"module": "node16",
55
"outDir": "./dist",
6-
"rootDir": "./src",
6+
"rootDir": "src",
77
"strict": true,
88
"skipLibCheck": true,
99
"forceConsistentCasingInFileNames": true
1010
},
11-
"include": ["src"]
11+
"include": ["src/**/*"],
12+
"exclude": ["tests/**/*", "src/**/*.test.ts"]
1213
}

0 commit comments

Comments
 (0)