feat: correctly subs out ability to boot REST API within same process

This commit is contained in:
Jarrod Flesch
2024-02-01 14:07:13 -05:00
parent e96c7bf987
commit 24feace60b
16 changed files with 2551 additions and 2714 deletions

View File

@@ -13,3 +13,31 @@ module.exports = {
},
verbose: true,
}
// // NextJS way of doing it
// const path = require('path')
// const nextJest = require('next/jest')
// // Optionally provide path to Next.js app which will enable loading next.config.js and .env files
// const createJestConfig = nextJest({ dir: path.resolve(__dirname, './test/REST_API') })
// // Any custom config you want to pass to Jest
// const customJestConfig = {
// globalSetup: './test/jest.setup.ts',
// moduleNameMapper: {
// '\\.(css|scss)$': '<rootDir>/packages/payload/src/bundlers/mocks/emptyModule.js',
// '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
// '<rootDir>/packages/payload/src/bundlers/mocks/fileMock.js',
// },
// testEnvironment: 'node',
// testMatch: ['<rootDir>/packages/payload/src/**/*.spec.ts', '<rootDir>/test/**/*int.spec.ts'],
// testTimeout: 90000,
// transform: {
// '^.+\\.(t|j)sx?$': ['@swc/jest'],
// },
// verbose: true,
// }
// // createJestConfig is exported in this way to ensure that next/jest can load the Next.js config which is async
// module.exports = createJestConfig(customJestConfig)

View File

@@ -81,7 +81,7 @@
"lint-staged": "^14.0.1",
"minimist": "1.2.8",
"mongodb-memory-server": "8.13.0",
"next": "14.0.2",
"next": "14.1.1-canary.26",
"node-fetch": "2.6.12",
"nodemon": "3.0.2",
"prettier": "^3.0.3",
@@ -102,7 +102,6 @@
},
"peerDependencies": {
"react": "18.2.0",
"react-i18next": "11.18.6",
"react-router-dom": "5.3.4"
},
"engines": {

View File

@@ -24,7 +24,7 @@ export const connect: Connect = async function connect(this: MongooseAdapter, pa
useFacet: undefined,
}
if (process.env.NODE_ENV === 'test') {
if ([process.env.APP_ENV, process.env.NODE_ENV].includes('test')) {
if (process.env.PAYLOAD_TEST_MONGO_URL) {
urlToConnect = process.env.PAYLOAD_TEST_MONGO_URL
} else {

View File

@@ -51,8 +51,7 @@
},
"peerDependencies": {
"http-status": "1.6.2",
"i18next": "22.5.1",
"next": "^14.0.0",
"next": "14.1.1-canary.26",
"payload": "^2.0.0"
},
"publishConfig": {

5047
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
packages:
# all packages in direct subdirs of packages/
- 'packages/*'
- 'test/REST_API'
# exclude packages that are inside test directories
- '!**/test/**'
# - '!**/test/**'

3
test/REST_API/.env.test Normal file
View File

@@ -0,0 +1,3 @@
PAYLOAD_SECRET=PAYLOAD_CUSTOM_SERVER_EXAMPLE_SECRET_KEY
APP_ENV=test
__NEXT_TEST_MODE=jest

5
test/REST_API/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,37 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
outputFileTracingExcludes: {
'**/*': ['drizzle-kit', 'drizzle-kit/utils'],
},
serverComponentsExternalPackages: [
'drizzle-kit',
'drizzle-kit/utils',
'pino',
'pino-pretty',
'mongodb-memory-server',
],
},
reactStrictMode: false,
// transpilePackages: ['@payloadcms/db-mongodb', 'mongoose'],
webpack: (config) => {
if (process.env.PAYLOAD_CONFIG_PATH) {
config.resolve.alias['payload-config'] = process.env.PAYLOAD_CONFIG_PATH
}
return {
...config,
externals: [
...config.externals,
'drizzle-kit',
'drizzle-kit/utils',
'pino',
'pino-pretty',
'mongoose',
'sharp',
],
}
},
}
module.exports = nextConfig

View File

@@ -0,0 +1,26 @@
{
"name": "dev",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@payloadcms/db-mongodb": "workspace:*",
"@payloadcms/next": "workspace:*",
"next": "14.1.1-canary.26",
"payload": "workspace:*",
"react": "18.3.0-canary-247738465-20240130",
"react-dom": "18.3.0-canary-247738465-20240130"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"sass": "^1.69.5",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,4 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
export { DELETE, GET, PATCH, POST } from '@payloadcms/next/routes'

View File

@@ -0,0 +1,3 @@
export const GET = async (request: Request) => {
return Response.json({ message: 'Hello world!' })
}

View File

@@ -0,0 +1,32 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"target": "ESNext",
"composite": true, // Make sure typescript knows that this module depends on their references
"allowImportingTsExtensions": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@payloadcms/db-mongodb": ["../../packages/db-mongodb/src"],
"@payloadcms/next/*": ["../../packages/next/src/*"]
}
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"composite": true, // Make sure typescript knows that this module depends on their references
"references": [{ "path": "../../packages/next" }, { "path": "../../packages/db-mongodb" }]
}

View File

@@ -1,9 +1,9 @@
import { GraphQLClient } from 'graphql-request'
import jwtDecode from 'jwt-decode'
import type { Payload } from '../../packages/payload/src'
import type { User } from '../../packages/payload/src/auth'
import payload from '../../packages/payload/src'
import configPromise from '../collections-graphql/config'
import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
@@ -13,6 +13,7 @@ require('isomorphic-fetch')
let apiUrl
let client: GraphQLClient
let payload: Payload
const headers = {
'Content-Type': 'application/json',
@@ -22,7 +23,11 @@ const { email, password } = devUser
describe('Auth', () => {
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
const { serverURL, payload: payloadClient } = await initPayloadTest({
__dirname,
init: { local: false },
})
payload = payloadClient
apiUrl = `${serverURL}/api`
const config = await configPromise
const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`
@@ -653,6 +658,14 @@ describe('Auth', () => {
})
})
describe('REST API', () => {
it('should respond from route handlers', async () => {
const test = await fetch(`${apiUrl}/api/test`)
expect(test.status).toStrictEqual(200)
})
})
describe('API Key', () => {
it('should authenticate via the correct API key user', async () => {
const usersQuery = await payload.find({

View File

@@ -1,13 +1,15 @@
import swcRegister from '@swc/register'
import express from 'express'
import getPort from 'get-port'
import { createServer } from 'http'
import next from 'next'
import path from 'path'
import shelljs from 'shelljs'
import { parse } from 'url'
import type { Payload } from '../../packages/payload/src'
import type { InitOptions } from '../../packages/payload/src/config/types'
import payload from '../../packages/payload/src'
import { getPayload } from '../../packages/payload/src'
type Options = {
__dirname: string
@@ -40,10 +42,6 @@ export async function initPayloadTest(options: Options): Promise<InitializedPayl
process.env.PAYLOAD_DROP_DATABASE = 'true'
process.env.NODE_ENV = 'test'
if (!initOptions?.local) {
initOptions.express = express()
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - bad @swc/register types
swcRegister({
@@ -59,12 +57,40 @@ export async function initPayloadTest(options: Options): Promise<InitializedPayl
},
})
await payload.init(initOptions)
const payload = await getPayload(initOptions)
const port = await getPort()
if (initOptions.express) {
initOptions.express.listen(port)
const serverURL = `http://localhost:${port}`
if (!initOptions?.local) {
// when using middleware `hostname` and `port` must be provided below
const app = next({
dev: true,
hostname: 'localhost',
port,
dir: path.resolve(__dirname, '../REST_API'),
})
const handle = app.getRequestHandler()
await app.prepare()
createServer(async (req, res) => {
try {
const parsedUrl = parse(req.url, true)
await handle(req, res, parsedUrl)
} catch (err) {
console.error('Error occurred handling', req.url, err)
res.statusCode = 500
res.end('internal server error')
}
})
.once('error', (err) => {
console.error(err)
process.exit(1)
})
.listen(port, () => {
console.log(`> Ready on ${serverURL}`)
})
}
return { serverURL: `http://localhost:${port}`, payload }
return { serverURL, payload }
}

View File

@@ -4,7 +4,6 @@ import payload from '../../packages/payload/src'
import { AuthenticationError } from '../../packages/payload/src/errors'
import { devUser, regularUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
import { RESTClient } from '../helpers/rest'
import { afterOperationSlug } from './collections/AfterOperation'
import { chainingHooksSlug } from './collections/ChainingHooks'
import { contextHooksSlug } from './collections/ContextHooks'
@@ -17,17 +16,14 @@ import {
import { relationsSlug } from './collections/Relations'
import { transformSlug } from './collections/Transform'
import { hooksUsersSlug } from './collections/Users'
import configPromise, { HooksConfig } from './config'
import { HooksConfig } from './config'
import { dataHooksGlobalSlug } from './globals/Data'
let client: RESTClient
let apiUrl
describe('Hooks', () => {
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
const config = await configPromise
client = new RESTClient(config, { serverURL, defaultSlug: transformSlug })
apiUrl = `${serverURL}/api`
})