feat: initial test suite framework (#4929)

This commit is contained in:
Jarrod Flesch
2024-02-14 09:46:11 -05:00
committed by GitHub
parent 018755516b
commit 717a6b6d07
50 changed files with 663 additions and 486 deletions

View File

@@ -0,0 +1,3 @@
export default typeof process.env.PAYLOAD_CONFIG_PATH === 'string'
? require(process.env.PAYLOAD_CONFIG_PATH)
: {}

View File

@@ -1,9 +1,10 @@
module.exports = {
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',
'payload-config': '<rootDir>/__mocks__/payload-config.ts',
},
testEnvironment: 'node',
testMatch: ['<rootDir>/packages/payload/src/**/*.spec.ts', '<rootDir>/test/**/*int.spec.ts'],
@@ -13,3 +14,5 @@ module.exports = {
},
verbose: true,
}
module.exports = customJestConfig

View File

@@ -80,16 +80,18 @@
"lexical": "0.12.5",
"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",
"pino": "8.15.0",
"pino-pretty": "10.2.0",
"prettier": "^3.0.3",
"prompts": "2.4.2",
"qs": "6.11.2",
"read-stream": "^2.1.1",
"rimraf": "3.0.2",
"semver": "^7.5.4",
"sharp": "0.32.6",
"shelljs": "0.8.5",
"simple-git": "^3.20.0",
"slash": "3.0.0",
@@ -102,7 +104,6 @@
},
"peerDependencies": {
"react": "18.2.0",
"react-i18next": "11.18.6",
"react-router-dom": "5.3.4"
},
"engines": {

View File

@@ -27,18 +27,17 @@
"prepublishOnly": "pnpm clean && pnpm build"
},
"dependencies": {
"bson-ext": "^4.0.3",
"bson-objectid": "2.0.4",
"deepmerge": "4.3.1",
"get-port": "5.1.1",
"mongoose": "6.12.0",
"mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22",
"prompts": "2.4.2",
"uuid": "9.0.0"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@types/mongoose-aggregate-paginate-v2": "1.0.9",
"mongodb-memory-server": "8.13.0",
"payload": "workspace:*"
},

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

@@ -4,7 +4,6 @@ import type { Init } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import mongoose from 'mongoose'
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'
import paginate from 'mongoose-paginate-v2'
import {
buildVersionCollectionFields,
@@ -45,10 +44,6 @@ export const init: Init = async function init(this: MongooseAdapter) {
}),
)
if (collection.versions?.drafts) {
versionSchema.plugin(mongooseAggregatePaginate)
}
const model = mongoose.model(
versionModelName,
versionSchema,

View File

@@ -1,11 +1,4 @@
import type {
AggregatePaginateModel,
IndexDefinition,
IndexOptions,
Model,
PaginateModel,
SchemaOptions,
} from 'mongoose'
import type { IndexDefinition, IndexOptions, Model, PaginateModel, SchemaOptions } from 'mongoose'
import type { Payload } from 'payload'
import type { SanitizedConfig } from 'payload/config'
import type {
@@ -34,11 +27,7 @@ import type {
import type { BuildQueryArgs } from './queries/buildQuery'
export interface CollectionModel
extends Model<any>,
PaginateModel<any>,
AggregatePaginateModel<any>,
PassportLocalModel {
export interface CollectionModel extends Model<any>, PaginateModel<any>, PassportLocalModel {
/** buildQuery is used to transform payload's where operator into what can be used by mongoose (e.g. id => _id) */
buildQuery: (args: BuildQueryArgs) => Promise<Record<string, unknown>> // TODO: Delete this
}

View File

@@ -286,7 +286,7 @@ export const upsertRow = async <T extends TypeWithID>({
message: req.t('error:valueMustBeUnique'),
},
],
req?.t ?? i18nInit(req.payload.config.i18n).t,
req.t,
)
: error
}

1
packages/dev/mocks.js Normal file
View File

@@ -0,0 +1 @@
export const mongooseAdapter = () => ({})

View File

@@ -8,8 +8,6 @@ const nextConfig = {
},
serverComponentsExternalPackages: ['drizzle-kit', 'drizzle-kit/utils', 'pino', 'pino-pretty'],
},
reactStrictMode: false,
// transpilePackages: ['@payloadcms/db-mongodb', 'mongoose'],
webpack: (config) => {
return {
...config,
@@ -19,15 +17,30 @@ const nextConfig = {
'drizzle-kit/utils',
'pino',
'pino-pretty',
'mongoose',
'sharp',
],
ignoreWarnings: [
...(config.ignoreWarnings || []),
{ module: /node_modules\/mongodb\/lib\/utils\.js/ },
{ file: /node_modules\/mongodb\/lib\/utils\.js/ },
],
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
graphql$: path.resolve(__dirname, '../next/node_modules/graphql/index.js'),
'graphql-http$': path.resolve(__dirname, '../next/node_modules/graphql-http/index.js'),
'payload-config$': path.resolve(process.env.PAYLOAD_CONFIG_PATH),
},
fallback: {
...config.resolve.fallback,
'@aws-sdk/credential-providers': false,
'@mongodb-js/zstd': false,
aws4: false,
kerberos: false,
'mongodb-client-encryption': false,
snappy: false,
'supports-color': false,
},
},
}

6
packages/dev/server.js Normal file
View File

@@ -0,0 +1,6 @@
const { bootAdminPanel } = require('../../test/helpers/bootAdminPanel.ts')
bootAdminPanel({
appDir: __dirname,
port: 3000,
})

View File

@@ -36,7 +36,6 @@ export default buildConfig({
// title: 'Test Page',
// },
// })
//
// await payload.update({
// collection: 'pages',
// id: page.id,

View File

@@ -17,21 +17,20 @@
"allowImportingTsExtensions": true,
"plugins": [
{
"name": "next"
}
"name": "next",
},
],
"paths": {
"payload": ["../payload/src"],
"payload/*": ["../payload/src/exports/*"],
"payload-config": ["./src/payload.config.ts"],
"@payloadcms/db-mongodb": ["../db-mongodb/src"],
"@payloadcms/richtext-lexical": ["../richtext-lexical/src"],
"@payloadcms/ui/*": ["../ui/src/exports/*"],
"@payloadcms/translations": ["../translations/src/exports/index.ts"],
"@payloadcms/translations/client": ["../translations/src/all"],
"@payloadcms/translations/api": ["../translations/src/all"],
"@payloadcms/next/*": ["../next/src/*"]
}
"@payloadcms/next/*": ["../next/src/*"],
},
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
@@ -43,6 +42,6 @@
{ "path": "../translations" },
{ "path": "../db-mongodb" },
{ "path": "../db-postgres" },
{ "path": "../richtext-lexical" }
]
{ "path": "../richtext-lexical" },
],
}

View File

@@ -2,5 +2,7 @@ import { PayloadRequest } from 'payload/types'
export type Context = {
req: PayloadRequest
headers: Headers
headers: {
[key: string]: string
}
}

View File

@@ -59,8 +59,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": {

View File

@@ -111,8 +111,13 @@ export const POST = (config: Promise<SanitizedConfig>) => async (request: Reques
validationRules: (request, args, defaultRules) => defaultRules.concat(validationRules(args)),
})(originalRequest)
const resHeaders = new Headers(apiResponse.headers)
for (let key in headers) {
resHeaders.append(key, headers[key])
}
return new Response(apiResponse.body, {
status: apiResponse.status,
headers: new Headers(headers),
headers: new Headers(resHeaders),
})
}

View File

@@ -7,7 +7,6 @@
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"paths": {
"payload-config": ["./src/config.ts"],
"@payloadcms/graphql": ["../graphql/dist/index.ts"],
"@payloadcms/ui": ["../ui/src/exports/index.ts"],
"@payloadcms/translations/*": ["../translations/dist/*"]

View File

@@ -9,7 +9,7 @@ export const APIKeyAuthentication =
async ({ headers, payload }) => {
const authHeader = headers.get('Authorization')
if (authHeader.startsWith(`${collectionConfig.slug} API-Key `)) {
if (authHeader?.startsWith(`${collectionConfig.slug} API-Key `)) {
const apiKey = authHeader.replace(`${collectionConfig.slug} API-Key `, '')
const apiKeyIndex = crypto.createHmac('sha1', payload.secret).update(apiKey).digest('hex')
@@ -38,8 +38,6 @@ export const APIKeyAuthentication =
collection: collectionConfig.slug,
depth: collectionConfig.auth.depth,
overrideAccess: true,
// TODO(JAMES)(REVIEW): had to remove with new pattern
// req,
where,
})

View File

@@ -8,8 +8,6 @@ export const build = async (): Promise<void> => {
disableOnInit: true,
local: true,
})
await payload.config.admin.bundler.build(payload.config)
}
// when build.js is launched directly

View File

@@ -238,7 +238,7 @@ export type PayloadHandler = ({
/**
* Docs: https://payloadcms.com/docs/rest-api/overview#custom-endpoints
*/
export type Endpoint = {
export type Endpoint<U = User> = {
/** Extension point to add your custom data. */
custom?: Record<string, any>
/**

View File

@@ -47,7 +47,6 @@ import { decrypt, encrypt } from './auth/crypto'
import { APIKeyAuthentication } from './auth/strategies/apiKey'
import { JWTAuthentication } from './auth/strategies/jwt'
import localOperations from './collections/operations/local'
import findConfig from './config/find'
import buildEmail from './email/build'
import { defaults as emailDefaults } from './email/defaults'
import sendEmail from './email/sendEmail'
@@ -311,14 +310,14 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
this.config = await options.config
// TODO(JARROD/JAMES): can we keep this?
const configPath = findConfig()
// const configPath = findConfig()
this.config = {
...this.config,
paths: {
config: configPath,
configDir: path.dirname(configPath),
rawConfig: configPath,
},
// paths: {
// config: configPath,
// configDir: path.dirname(configPath),
// rawConfig: configPath,
// },
}
if (!this.config.secret) {

View File

@@ -1,7 +1,7 @@
'use client'
import type { ElementType } from 'react'
import { Tooltip } from 'payload/components'
import { Tooltip } from '@payloadcms/ui'
import React, { useCallback, useState } from 'react'
import { useSlate } from 'slate-react'

View File

@@ -1,5 +1,5 @@
'use client'
import { ShimmerEffect } from 'payload/components'
import { ShimmerEffect } from '@payloadcms/ui'
import React, { Suspense, lazy } from 'react'
import type { FieldProps } from '../types'

View File

@@ -1,6 +1,7 @@
import type { RichTextAdapter } from 'payload/types'
import { withMergedProps, withNullableJSONSchemaType } from 'payload/utilities'
import { withMergedProps } from '@payloadcms/ui/utilities'
import { withNullableJSONSchemaType } from 'payload/utilities'
import type { AdapterArguments } from './types'

View File

@@ -34,3 +34,4 @@ export { useDrawerSlug } from '../elements/Drawer/useDrawerSlug'
export { default as Popup } from '../elements/Popup'
// export { useThumbnail } from '../elements/Upload'
export { Translation } from '../elements/Translation'
export { Tooltip } from '../elements/Tooltip'

View File

@@ -290,11 +290,10 @@ const Relationship: React.FC<Props> = (props) => {
await priorRelation
const idsToLoad = ids.filter((id) => {
return !options.find(
(optionGroup) =>
optionGroup?.options?.find(
(option) => option.value === id && option.relationTo === relation,
),
return !options.find((optionGroup) =>
optionGroup?.options?.find(
(option) => option.value === id && option.relationTo === relation,
),
)
})

643
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/**'

View File

@@ -1,26 +1,34 @@
import payload from '../../packages/payload/src'
import type { Payload } from '../../packages/payload/src'
import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers'
import { postsSlug } from './collections/Posts'
require('isomorphic-fetch')
let apiUrl
let payload: Payload
let apiURL: string
let jwt
const headers = {
'Content-Type': 'application/json',
}
const { email, password } = devUser
describe('_Community Tests', () => {
// --__--__--__--__--__--__--__--__--__
// Boilerplate test setup/teardown
// --__--__--__--__--__--__--__--__--__
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
apiUrl = `${serverURL}/api`
const { payload: payloadClient, serverURL } = await initPayloadTest({
__dirname,
init: { local: false },
})
const response = await fetch(`${apiUrl}/users/login`, {
apiURL = `${serverURL}/api`
payload = payloadClient
const response = await fetch(`${apiURL}/users/login`, {
body: JSON.stringify({
email,
password,
@@ -56,7 +64,7 @@ describe('_Community Tests', () => {
})
it('rest API example', async () => {
const newPost = await fetch(`${apiUrl}/${postsSlug}`, {
const newPost = await fetch(`${apiURL}/${postsSlug}`, {
method: 'POST',
headers: {
...headers,

View File

@@ -1,9 +1,11 @@
'use client'
import React, { useEffect, useState } from 'react'
import type { User } from '../../packages/payload/src/auth'
import type { UIField } from '../../packages/payload/src/fields/config/types'
import { useAuth } from '../../packages/payload/src/admin/components/utilities/Auth'
import { useAuth } from '../../packages/ui'
export const AuthDebug: React.FC<UIField> = () => {
const [state, setState] = useState<User | null | undefined>()

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}`
@@ -35,6 +40,10 @@ describe('Auth', () => {
}
})
beforeEach(() => {
jest.resetModules()
})
describe('GraphQL - admin user', () => {
let token
let user
@@ -653,6 +662,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

@@ -2,8 +2,6 @@ import path from 'path'
import type { Config, SanitizedConfig } from '../packages/payload/src/config/types'
import { viteBundler } from '../packages/bundler-vite/src'
import { webpackBundler } from '../packages/bundler-webpack/src'
import { mongooseAdapter } from '../packages/db-mongodb/src'
import { postgresAdapter } from '../packages/db-postgres/src'
import { buildConfig as buildPayloadConfig } from '../packages/payload/src/config/build'
@@ -11,11 +9,6 @@ import { slateEditor } from '../packages/richtext-slate/src'
// process.env.PAYLOAD_DATABASE = 'postgres'
const bundlerAdapters = {
vite: viteBundler(),
webpack: webpackBundler(),
}
const databaseAdapters = {
mongoose: mongooseAdapter({
migrationDir: path.resolve(__dirname, '../packages/db-mongodb/migrations'),
@@ -30,10 +23,9 @@ const databaseAdapters = {
}
export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<SanitizedConfig> {
const [name] = process.argv.slice(2)
const config: Config = {
editor: slateEditor({}),
secret: 'TEST_SECRET',
editor: undefined,
rateLimit: {
max: 9999999999,
window: 15 * 60 * 1000, // 15min default,
@@ -53,49 +45,6 @@ export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<S
},
...(config.admin || {}),
buildPath: path.resolve(__dirname, '../build'),
bundler: bundlerAdapters[process.env.PAYLOAD_BUNDLER || 'webpack'],
webpack: (webpackConfig) => {
const existingConfig =
typeof testConfig?.admin?.webpack === 'function'
? testConfig.admin.webpack(webpackConfig)
: webpackConfig
return {
...existingConfig,
name,
cache: process.env.NODE_ENV === 'test' ? { type: 'memory' } : existingConfig.cache,
entry: {
main: [
`webpack-hot-middleware/client?path=${
testConfig?.routes?.admin || '/admin'
}/__webpack_hmr`,
path.resolve(__dirname, '../packages/payload/src/admin'),
],
},
resolve: {
...existingConfig.resolve,
alias: {
...existingConfig.resolve?.alias,
[path.resolve(__dirname, '../packages/bundler-vite/src/index')]: path.resolve(
__dirname,
'../packages/bundler-vite/mock.js',
),
[path.resolve(__dirname, '../packages/bundler-webpack/src/index')]: path.resolve(
__dirname,
'../packages/bundler-webpack/src/mocks/emptyModule.js',
),
[path.resolve(__dirname, '../packages/db-mongodb/src/index')]: path.resolve(
__dirname,
'../packages/db-mongodb/mock.js',
),
[path.resolve(__dirname, '../packages/db-postgres/src/index')]: path.resolve(
__dirname,
'../packages/db-postgres/mock.js',
),
react: path.resolve(__dirname, '../packages/payload/node_modules/react'),
},
},
}
},
}
if (process.env.PAYLOAD_DISABLE_ADMIN === 'true') {

View File

@@ -40,7 +40,7 @@ export default buildConfigWithDefaults({
{
path: '/send-test-email',
method: 'get',
handler: async (req, res) => {
handler: async ({ req }) => {
await req.payload.sendEmail({
from: 'dev@payloadcms.com',
to: devUser.email,
@@ -55,19 +55,15 @@ export default buildConfigWithDefaults({
// },
})
res.status(200).send('Email sent')
return Response.json({ message: 'Email sent' })
},
},
{
path: '/internal-error-here',
method: 'get',
handler: async (req, res, next) => {
try {
// Throwing an internal error with potentially sensitive data
throw new Error('Lost connection to the Pentagon. Secret data: ******')
} catch (err) {
next(err)
}
handler: () => {
// Throwing an internal error with potentially sensitive data
throw new Error('Lost connection to the Pentagon. Secret data: ******')
},
},
],

View File

@@ -15,8 +15,8 @@ export default buildConfigWithDefaults({
{
path: '/hello',
method: 'get',
handler: (_, res): void => {
res.json({ message: 'hi' })
handler: () => {
return Response.json({ message: 'hi' })
},
custom: { examples: [{ type: 'response', value: { message: 'hi' } }] },
},
@@ -38,9 +38,9 @@ export default buildConfigWithDefaults({
{
path: '/greet',
method: 'get',
handler: (req, res): void => {
const { name } = req.query
res.json({ message: `Hi ${name}!` })
handler: ({ req }) => {
const sp = new URL(req.url).searchParams
return Response.json({ message: `Hi ${sp.get('name')}!` })
},
custom: { params: [{ in: 'query', name: 'name', type: 'string' }] },
},
@@ -60,8 +60,8 @@ export default buildConfigWithDefaults({
path: '/config',
method: 'get',
root: true,
handler: (req, res): void => {
res.json(req.payload.config)
handler: ({ req }) => {
return Response.json(req.payload.config)
},
custom: { description: 'Get the sanitized payload config' },
},

View File

@@ -29,7 +29,7 @@ describe('Config', () => {
it('allows a custom field in collection endpoints', () => {
const [collection] = payload.config.collections
const [endpoint] = collection.endpoints
const [endpoint] = collection.endpoints || []
expect(endpoint.custom).toEqual({
examples: [{ type: 'response', value: { message: 'hi' } }],
@@ -52,7 +52,7 @@ describe('Config', () => {
it('allows a custom field in global endpoints', () => {
const [global] = payload.config.globals
const [endpoint] = global.endpoints
const [endpoint] = global.endpoints || []
expect(endpoint.custom).toEqual({ params: [{ in: 'query', name: 'name', type: 'string' }] })
})

View File

@@ -1,7 +1,7 @@
import { GraphQLClient } from 'graphql-request'
import type { TypeWithID } from '../../packages/payload/src/collections/config/types'
import type { PayloadRequest } from '../../packages/payload/src/express/types'
import type { PayloadRequest } from '../../packages/payload/src/types'
import payload from '../../packages/payload/src'
import { commitTransaction } from '../../packages/payload/src/utilities/commitTransaction'

View File

@@ -1,11 +1,10 @@
import * as dotenv from 'dotenv'
import express from 'express'
import fs from 'fs'
import path from 'path'
import { v4 as uuid } from 'uuid'
import payload from '../packages/payload/src'
import { getPayload } from '../packages/payload/src'
import { prettySyncLoggerDestination } from '../packages/payload/src/utilities/logger'
import { bootAdminPanel } from './helpers/bootAdminPanel'
import { startLivePreviewDemo } from './live-preview/startLivePreviewDemo'
dotenv.config()
@@ -46,42 +45,30 @@ if (process.argv.includes('--no-auto-login') && process.env.NODE_ENV !== 'produc
process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN = 'true'
}
const expressApp = express()
const startDev = async () => {
await payload.init({
const payload = await getPayload({
email: {
fromAddress: 'hello@payloadcms.com',
fromName: 'Payload',
logMockCredentials: false,
},
express: expressApp,
secret: uuid(),
config: require(configPath).default,
...prettySyncLogger,
onInit: async (payload) => {
onInit: (payload) => {
payload.logger.info('Payload Dev Server Initialized')
},
})
// Redirect root to Admin panel
expressApp.get('/', (_, res) => {
res.redirect('/admin')
})
const externalRouter = express.Router()
externalRouter.use(payload.authenticate)
if (testSuiteDir === 'live-preview') {
await startLivePreviewDemo({
payload,
})
}
expressApp.listen(3000, async () => {
payload.logger.info(`Admin URL on http://localhost:3000${payload.getAdminURL()}`)
payload.logger.info(`API URL on http://localhost:3000${payload.getAPIURL()}`)
})
await bootAdminPanel({ appDir: path.resolve(__dirname, '../packages/dev') })
payload.logger.info(`Admin URL on http://localhost:3000${payload.getAdminURL()}`)
payload.logger.info(`API URL on http://localhost:3000${payload.getAPIURL()}`)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises

View File

@@ -1,5 +1,3 @@
import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
import { collectionEndpoints } from './endpoints/collections'
@@ -61,20 +59,6 @@ export default buildConfigWithDefaults({
},
],
endpoints,
admin: {
webpack: (config) => {
return {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
express: path.resolve(__dirname, './mocks/emptyModule.js'),
},
},
}
},
},
onInit: async (payload) => {
await payload.create({
collection: 'users',

View File

@@ -1,37 +1,34 @@
import type { Response } from 'express'
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import type { PayloadRequest } from '../../../packages/payload/src/express/types'
export const collectionEndpoints: CollectionConfig['endpoints'] = [
{
path: '/say-hello/joe-bloggs',
method: 'get',
handler: (req: PayloadRequest, res: Response): void => {
res.json({ message: 'Hey Joey!' })
handler: () => {
return Response.json({ message: 'Hey Joey!' })
},
},
{
path: '/say-hello/:group/:name',
method: 'get',
handler: (req: PayloadRequest, res: Response): void => {
res.json({ message: `Hello ${req.params.name} @ ${req.params.group}` })
handler: ({ routeParams }) => {
return Response.json({ message: `Hello ${routeParams.name} @ ${routeParams.group}` })
},
},
{
path: '/say-hello/:name',
method: 'get',
handler: (req: PayloadRequest, res: Response): void => {
res.json({ message: `Hello ${req.params.name}!` })
handler: ({ routeParams }) => {
return Response.json({ message: `Hello ${routeParams.name}!` })
},
},
{
path: '/whoami',
method: 'post',
handler: (req: PayloadRequest, res: Response): void => {
res.json({
name: req.body.name,
age: req.body.age,
handler: ({ req }) => {
return Response.json({
name: req.data.name,
age: req.data.age,
})
},
},

View File

@@ -1,6 +1,3 @@
import type { Response } from 'express'
import type { PayloadRequest } from '../../../packages/payload/src/express/types'
import type { GlobalConfig } from '../../../packages/payload/src/globals/config/types'
import { globalEndpoint } from '../shared'
@@ -9,8 +6,8 @@ export const globalEndpoints: GlobalConfig['endpoints'] = [
{
path: `/${globalEndpoint}`,
method: 'post',
handler: (req: PayloadRequest, res: Response): void => {
res.json(req.body)
handler: ({ req }) => {
return Response.json(req.body)
},
},
]

View File

@@ -1,9 +1,4 @@
import type { Response } from 'express'
import express from 'express'
import type { Config } from '../../../packages/payload/src/config/types'
import type { PayloadRequest } from '../../../packages/payload/src/express/types'
import { applicationEndpoint, rootEndpoint } from '../shared'
@@ -11,41 +6,38 @@ export const endpoints: Config['endpoints'] = [
{
path: `/${applicationEndpoint}`,
method: 'post',
handler: (req: PayloadRequest, res: Response): void => {
res.json(req.body)
handler: ({ req }) => {
return Response.json(req.body)
},
},
{
path: `/${applicationEndpoint}`,
method: 'get',
handler: (req: PayloadRequest, res: Response): void => {
res.json({ message: 'Hello, world!' })
handler: ({ req }) => {
return Response.json({ message: 'Hello, world!' })
},
},
{
path: `/${applicationEndpoint}/i18n`,
method: 'get',
handler: (req: PayloadRequest, res: Response): void => {
res.json({ message: req.t('general:backToDashboard') })
handler: ({ req }) => {
return Response.json({ message: req.t('general:backToDashboard') })
},
},
{
path: `/${rootEndpoint}`,
method: 'get',
root: true,
handler: (req: PayloadRequest, res: Response): void => {
res.json({ message: 'Hello, world!' })
handler: () => {
return Response.json({ message: 'Hello, world!' })
},
},
{
path: `/${rootEndpoint}`,
method: 'post',
root: true,
handler: [
express.json({ type: 'application/json' }),
(req: PayloadRequest, res: Response): void => {
res.json(req.body)
},
],
handler: ({ req }) => {
return Response.json(req.body)
},
},
]

View File

@@ -0,0 +1,39 @@
import { createServer } from 'http'
import next from 'next'
import { parse } from 'url'
type args = {
appDir: string
port?: number
}
export const bootAdminPanel = async ({ port = 3000, appDir }: args) => {
const serverURL = `http://localhost:${port}`
const app = next({
dev: true,
hostname: 'localhost',
port,
dir: appDir,
})
const handle = app.getRequestHandler()
await app.prepare()
createServer(async (req, res) => {
try {
const parsedUrl = parse(req.url, true)
console.log('Requested path: ', parsedUrl.path)
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}`)
})
}

View File

@@ -1,14 +1,12 @@
import swcRegister from '@swc/register'
import express from 'express'
import getPort from 'get-port'
import path from 'path'
import shelljs from 'shelljs'
import { v4 as uuid } from 'uuid'
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'
import { bootAdminPanel } from './bootAdminPanel'
type Options = {
__dirname: string
@@ -29,42 +27,31 @@ export async function initPayloadE2E(__dirname: string): Promise<InitializedPayl
}
export async function initPayloadTest(options: Options): Promise<InitializedPayload> {
const initOptions = {
process.env.PAYLOAD_CONFIG_PATH = path.resolve(options.__dirname, './config.ts')
const initOptions: InitOptions = {
local: true,
secret: uuid(),
mongoURL: `mongodb://localhost/${uuid()}`,
config: require(process.env.PAYLOAD_CONFIG_PATH).default,
// loggerOptions: {
// enabled: false,
// },
...(options.init || {}),
}
process.env.PAYLOAD_DROP_DATABASE = 'true'
process.env.NODE_ENV = 'test'
process.env.PAYLOAD_CONFIG_PATH = path.resolve(options.__dirname, './config.ts')
if (!initOptions?.local) {
initOptions.express = express()
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - bad @swc/register types
swcRegister({
sourceMaps: 'inline',
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
},
module: {
type: 'commonjs',
},
})
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) {
process.env.APP_ENV = 'test'
process.env.__NEXT_TEST_MODE = 'jest'
await bootAdminPanel({ port, appDir: path.resolve(__dirname, '../../packages/dev') })
jest.resetModules()
}
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`
})

View File

@@ -1,5 +1,5 @@
import type { Payload } from '../../../packages/payload/src'
import type { PayloadRequest } from '../../../packages/payload/src/express/types'
import type { PayloadRequest } from '../../../packages/payload/src/types'
import { formsSlug, pagesSlug } from '../shared'

View File

@@ -1,5 +1,5 @@
import type { Payload } from '../../../packages/payload/src'
import type { PayloadRequest } from '../../../packages/payload/src/express/types'
import type { PayloadRequest } from '../../../packages/payload/src/types'
export const seed = async (payload: Payload): Promise<boolean> => {
payload.logger.info('Seeding data...')

View File

@@ -1,5 +1,5 @@
import wait from '../../packages/payload/dist/utilities/wait'
import payload from '../../packages/payload/src'
import wait from '../../packages/payload/src/utilities/wait'
import { initPayloadTest } from '../helpers/configHelpers'
describe('Search Plugin', () => {

View File

@@ -1,6 +1,6 @@
import { randomBytes } from 'crypto'
import type { PayloadRequest } from '../../packages/payload/src/express/types'
import type { PayloadRequest } from '../../packages/payload/src/types'
import type {
ChainedRelation,
CustomIdNumberRelation,

View File

@@ -4,7 +4,7 @@
"noEmit": false /* Do not emit outputs. */,
"emitDeclarationOnly": true,
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"rootDir": "../" /* Specify the root folder within your source files. */
"rootDir": "../" /* Specify the root folder within your source files. */,
},
"exclude": ["dist", "build", "node_modules", ".eslintrc.js"],
"include": [
@@ -14,6 +14,11 @@
"src/**/*.json",
"**/*.ts",
"test/**/*.tsx",
"../packages/**/src/**/*.ts"
]
"../packages/**/src/**/*.ts",
],
"plugins": [
{
"name": "next",
},
],
}

View File

@@ -1,5 +1,3 @@
import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import AutosavePosts from './collections/Autosave'
import DisablePublish from './collections/DisablePublish'
@@ -12,18 +10,6 @@ import DraftGlobal from './globals/Draft'
import { clearAndSeedEverything } from './seed'
export default buildConfigWithDefaults({
admin: {
webpack: (config) => ({
...config,
resolve: {
...config.resolve,
alias: {
...config?.resolve?.alias,
fs: path.resolve(__dirname, './mocks/emptyModule.js'),
},
},
}),
},
collections: [DisablePublish, Posts, AutosavePosts, DraftPosts, VersionPosts],
globals: [AutosaveGlobal, DraftGlobal, DisablePublishGlobal],
indexSortableFields: true,