chore: local api sdk for e2e tests

This commit is contained in:
James
2024-04-01 21:53:30 -04:00
parent cd553d45cc
commit 94d0e28ad7
10 changed files with 273 additions and 13 deletions

2
.vscode/launch.json vendored
View File

@@ -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",

View File

@@ -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'

7
pnpm-lock.yaml generated
View File

@@ -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==}

View File

@@ -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<Config>
/**
* TODO: Auth
@@ -34,7 +38,7 @@ describe('auth', () => {
let apiURL: string
beforeAll(async ({ browser }) => {
;({ serverURL, payload } = await initPayloadE2E({ dirname }))
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
apiURL = `${serverURL}/api`
url = new AdminUrlUtil(serverURL, slug)

View File

@@ -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(),

View File

@@ -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<T extends GeneratedTypes<T>> = {
payload: PayloadTestSDK<T>
serverURL: string
}
export async function initPayloadE2ENoConfig<T extends GeneratedTypes<T>>({
dirname,
}: Args): Promise<Result<T>> {
// @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<T>({ serverURL }),
}
}

View File

@@ -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,
},
)
}

59
test/helpers/sdk/index.ts Normal file
View File

@@ -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<TGeneratedTypes extends GeneratedTypes<TGeneratedTypes>> {
create = async <T extends keyof TGeneratedTypes['collections']>({
jwt,
...args
}: CreateArgs<TGeneratedTypes, T>) => {
return this.fetch<TGeneratedTypes['collections'][T]>({
method: 'create',
args,
jwt,
})
}
fetch = async <T>({ jwt, reduceJSON, args, method }: FetchOptions): Promise<T> => {
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<T>(json)
return json
}
find = async <T extends keyof TGeneratedTypes['collections']>({
jwt,
...args
}: FindArgs<TGeneratedTypes, T>) => {
return this.fetch<PaginatedDocs<TGeneratedTypes['collections'][T]>>({
method: 'find',
args,
jwt,
reduceJSON: (json) => json.docs,
})
}
serverURL: string
constructor({ serverURL }: Args) {
this.serverURL = serverURL
}
}

75
test/helpers/sdk/types.ts Normal file
View File

@@ -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<string, CollectionDoc>
globals: Record<string, TypeWithID>
}
export type GeneratedTypes<T extends BaseTypes> = {
collections: {
[P in keyof T['collections']]: CollectionDoc
}
globals: {
[P in keyof T['globals']]: T['globals'][P]
}
}
export type FetchOptions = {
args?: Record<string, unknown>
jwt?: string
method: 'create' | 'find'
reduceJSON?: <R>(json: any) => R
}
type BaseArgs = {
jwt?: string
}
export type CreateArgs<
TGeneratedTypes extends GeneratedTypes<TGeneratedTypes>,
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<TGeneratedTypes>,
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

View File

@@ -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"
}
}