From 94d0e28ad73873bd3d0343f28144cd26ddb5eef7 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 1 Apr 2024 21:53:30 -0400 Subject: [PATCH] chore: local api sdk for e2e tests --- .vscode/launch.json | 2 +- .../collections/operations/local/create.ts | 2 +- pnpm-lock.yaml | 7 +- test/auth/e2e.spec.ts | 12 ++- test/buildConfigWithDefaults.ts | 9 +++ test/helpers/initPayloadE2ENoConfig.ts | 72 ++++++++++++++++++ test/helpers/sdk/handler.ts | 34 +++++++++ test/helpers/sdk/index.ts | 59 +++++++++++++++ test/helpers/sdk/types.ts | 75 +++++++++++++++++++ test/package.json | 14 ++-- 10 files changed, 273 insertions(+), 13 deletions(-) create mode 100644 test/helpers/initPayloadE2ENoConfig.ts create mode 100644 test/helpers/sdk/handler.ts create mode 100644 test/helpers/sdk/index.ts create mode 100644 test/helpers/sdk/types.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index ae3fd612a5..69cd64fd2d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "cwd": "${workspaceFolder}" }, { - "command": "node --no-deprecation test/dev.js fields", + "command": "node --no-deprecation test/dev.js _community", "cwd": "${workspaceFolder}", "name": "Run Dev Community", "request": "launch", diff --git a/packages/payload/src/collections/operations/local/create.ts b/packages/payload/src/collections/operations/local/create.ts index 0886ef4918..6efd40d734 100644 --- a/packages/payload/src/collections/operations/local/create.ts +++ b/packages/payload/src/collections/operations/local/create.ts @@ -1,6 +1,6 @@ import type { MarkOptional } from 'ts-essentials' -import type { GeneratedTypes } from '../../..//index.js' +import type { GeneratedTypes } from '../../../index.js' import type { Payload } from '../../../index.js' import type { Document, PayloadRequest, RequestContext } from '../../../types/index.js' import type { File } from '../../../uploads/types.js' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cc10d7b6d..13be8143d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1571,12 +1571,18 @@ importers: execa: specifier: 5.1.1 version: 5.1.1 + http-status: + specifier: 1.6.2 + version: 1.6.2 payload: specifier: workspace:* version: link:../packages/payload tempy: specifier: ^1.0.1 version: 1.0.1 + ts-essentials: + specifier: 7.0.3 + version: 7.0.3(typescript@5.4.2) typescript: specifier: 5.4.2 version: 5.4.2 @@ -10920,7 +10926,6 @@ packages: /http-status@1.6.2: resolution: {integrity: sha512-oUExvfNckrpTpDazph7kNG8sQi5au3BeTo0idaZFXEhTaJKu7GNJCLHI0rYY2wljm548MSTM+Ljj/c6anqu2zQ==} engines: {node: '>= 0.4.0'} - dev: false /http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} diff --git a/test/auth/e2e.spec.ts b/test/auth/e2e.spec.ts index 698016378c..8d006fd2d7 100644 --- a/test/auth/e2e.spec.ts +++ b/test/auth/e2e.spec.ts @@ -1,18 +1,22 @@ import type { Page } from '@playwright/test' -import type { Payload } from 'payload' import { expect, test } from '@playwright/test' import path from 'path' import { fileURLToPath } from 'url' +import type { PayloadTestSDK } from '../helpers/sdk/index.js' +import type { Config } from './payload-types.js' + import { initPageConsoleErrorCatch, login, saveDocAndAssert } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' -import { initPayloadE2E } from '../helpers/initPayloadE2E.js' +import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js' import { apiKeysSlug, slug } from './shared.js' + const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) -let payload: Payload + +let payload: PayloadTestSDK /** * TODO: Auth @@ -34,7 +38,7 @@ describe('auth', () => { let apiURL: string beforeAll(async ({ browser }) => { - ;({ serverURL, payload } = await initPayloadE2E({ dirname })) + ;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname })) apiURL = `${serverURL}/api` url = new AdminUrlUtil(serverURL, slug) diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index bc27806882..907a8a0fb5 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -29,6 +29,8 @@ import { MongoMemoryReplSet } from 'mongodb-memory-server' // import { slateEditor } from '@payloadcms/richtext-slate' import { type Config, buildConfig } from 'payload/config' import sharp from 'sharp' + +import { localAPIHandler } from './helpers/sdk/handler.js' // process.env.PAYLOAD_DATABASE = 'postgres' const databaseAdapters = { @@ -105,6 +107,13 @@ export async function buildConfigWithDefaults( // }, // }, // }), + endpoints: [ + { + path: '/local-api', + method: 'post', + handler: localAPIHandler, + }, + ], editor: lexicalEditor({ features: [ ParagraphFeature(), diff --git a/test/helpers/initPayloadE2ENoConfig.ts b/test/helpers/initPayloadE2ENoConfig.ts new file mode 100644 index 0000000000..a8cd561e79 --- /dev/null +++ b/test/helpers/initPayloadE2ENoConfig.ts @@ -0,0 +1,72 @@ +import { createServer } from 'http' +import nextImport from 'next' +import path from 'path' +import { wait } from 'payload/utilities' +import { parse } from 'url' + +import type { GeneratedTypes } from './sdk/types.js' + +import { createTestHooks } from '../testHooks.js' +import { PayloadTestSDK } from './sdk/index.js' + +type Args = { + dirname: string +} + +type Result> = { + payload: PayloadTestSDK + serverURL: string +} + +export async function initPayloadE2ENoConfig>({ + dirname, +}: Args): Promise> { + // @ts-expect-error + process.env.NODE_ENV = 'test' + process.env.NODE_OPTIONS = '--no-deprecation' + process.env.PAYLOAD_DROP_DATABASE = 'true' + + const testSuiteName = dirname.split('/').pop() + const { beforeTest } = await createTestHooks(testSuiteName) + await beforeTest() + + const port = 3000 + process.env.PORT = String(port) + const serverURL = `http://localhost:${port}` + + // @ts-expect-error + const app = nextImport({ + dev: true, + hostname: 'localhost', + port, + dir: path.resolve(dirname, '../../'), + }) + + const handle = app.getRequestHandler() + + let resolveServer + + const serverPromise = new Promise((res) => (resolveServer = res)) + + // Need a custom server because calling nextDev straight + // starts up a child process, and payload.onInit() is called twice + // which seeds test data twice + other bad things. + // We initialize Payload above so we can have access to it in the tests + void app.prepare().then(() => { + createServer(async (req, res) => { + const parsedUrl = parse(req.url, true) + await handle(req, res, parsedUrl) + }).listen(port, () => { + resolveServer() + }) + }) + + await serverPromise + + await wait(port) + + return { + serverURL, + payload: new PayloadTestSDK({ serverURL }), + } +} diff --git a/test/helpers/sdk/handler.ts b/test/helpers/sdk/handler.ts new file mode 100644 index 0000000000..10364d2e68 --- /dev/null +++ b/test/helpers/sdk/handler.ts @@ -0,0 +1,34 @@ +import type { PayloadRequest } from 'payload/types' + +import httpStatus from 'http-status' + +export const localAPIHandler = async ({ payload, data, user, body }: PayloadRequest) => { + const method = String(data.method) + + if (typeof payload[method] === 'function') { + try { + const result = await payload[method]({ + ...(typeof data.args === 'object' ? data.args : {}), + user, + }) + + return Response.json(result, { + status: httpStatus.OK, + }) + } catch (err) { + payload.logger.error(err) + return Response.json(err, { + status: httpStatus.BAD_REQUEST, + }) + } + } + + return Response.json( + { + message: 'Payload Local API method not found.', + }, + { + status: httpStatus.BAD_REQUEST, + }, + ) +} diff --git a/test/helpers/sdk/index.ts b/test/helpers/sdk/index.ts new file mode 100644 index 0000000000..0d4bf4e688 --- /dev/null +++ b/test/helpers/sdk/index.ts @@ -0,0 +1,59 @@ +import type { PaginatedDocs } from 'payload/database' + +import type { CreateArgs, FetchOptions, FindArgs, GeneratedTypes } from './types.js' + +type Args = { + serverURL: string +} + +export class PayloadTestSDK> { + create = async ({ + jwt, + ...args + }: CreateArgs) => { + return this.fetch({ + method: 'create', + args, + jwt, + }) + } + + fetch = async ({ jwt, reduceJSON, args, method }: FetchOptions): Promise => { + const headers: HeadersInit = { + 'Content-Type': 'application/json', + } + + if (jwt) headers.Authorization = `JWT ${jwt}` + + const json: T = await fetch(`${this.serverURL}/api/local-api`, { + method: 'post', + headers, + body: JSON.stringify({ + args, + method, + }), + }).then((res) => res.json()) + + if (reduceJSON) return reduceJSON(json) + + return json + } + + find = async ({ + jwt, + ...args + }: FindArgs) => { + return this.fetch>({ + method: 'find', + args, + jwt, + reduceJSON: (json) => json.docs, + }) + } + + serverURL: string + + constructor({ serverURL }: Args) { + this.serverURL = serverURL + } +} diff --git a/test/helpers/sdk/types.ts b/test/helpers/sdk/types.ts new file mode 100644 index 0000000000..b7e62090f9 --- /dev/null +++ b/test/helpers/sdk/types.ts @@ -0,0 +1,75 @@ +import type { TypeWithID, Where } from 'payload/types' +import type { MarkOptional } from 'ts-essentials' + +type CollectionDoc = { + createdAt?: string + id?: number | string + sizes?: unknown + updatedAt?: string +} + +type BaseTypes = { + collections: Record + globals: Record +} + +export type GeneratedTypes = { + collections: { + [P in keyof T['collections']]: CollectionDoc + } + globals: { + [P in keyof T['globals']]: T['globals'][P] + } +} + +export type FetchOptions = { + args?: Record + jwt?: string + method: 'create' | 'find' + reduceJSON?: (json: any) => R +} + +type BaseArgs = { + jwt?: string +} + +export type CreateArgs< + TGeneratedTypes extends GeneratedTypes, + TSlug extends keyof TGeneratedTypes['collections'], +> = { + collection: TSlug + data: MarkOptional< + TGeneratedTypes['collections'][TSlug], + 'createdAt' | 'id' | 'sizes' | 'updatedAt' + > + depth?: number + draft?: boolean + fallbackLocale?: string + file?: File + filePath?: string + locale?: string + overrideAccess?: boolean + overwriteExistingFiles?: boolean + showHiddenFields?: boolean + user?: TypeWithID +} & BaseArgs + +export type FindArgs< + TGeneratedTypes extends GeneratedTypes, + TSlug extends keyof TGeneratedTypes['collections'], +> = { + collection: TSlug + depth?: number + disableErrors?: boolean + draft?: boolean + fallbackLocale?: string + limit?: number + locale?: string + overrideAccess?: boolean + page?: number + pagination?: boolean + showHiddenFields?: boolean + sort?: string + user?: TypeWithID + where?: Where +} & BaseArgs diff --git a/test/package.json b/test/package.json index d649e9efb8..151f758d2e 100644 --- a/test/package.json +++ b/test/package.json @@ -32,13 +32,15 @@ "@payloadcms/richtext-slate": "workspace:*", "@payloadcms/translations": "workspace:*", "@payloadcms/ui": "workspace:*", - "create-payload-app": "workspace:*", - "eslint-plugin-playwright": "1.5.3", - "eslint-plugin-payload": "workspace:*", - "payload": "workspace:*", - "execa": "5.1.1", - "tempy": "^1.0.1", "comment-json": "^4.2.3", + "create-payload-app": "workspace:*", + "eslint-plugin-payload": "workspace:*", + "eslint-plugin-playwright": "1.5.3", + "execa": "5.1.1", + "http-status": "1.6.2", + "payload": "workspace:*", + "tempy": "^1.0.1", + "ts-essentials": "7.0.3", "typescript": "5.4.2" } }