chore: local api sdk for e2e tests
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -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",
|
||||
|
||||
@@ -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
7
pnpm-lock.yaml
generated
@@ -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==}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
72
test/helpers/initPayloadE2ENoConfig.ts
Normal file
72
test/helpers/initPayloadE2ENoConfig.ts
Normal 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 }),
|
||||
}
|
||||
}
|
||||
34
test/helpers/sdk/handler.ts
Normal file
34
test/helpers/sdk/handler.ts
Normal 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
59
test/helpers/sdk/index.ts
Normal 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
75
test/helpers/sdk/types.ts
Normal 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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user