chore: merge

This commit is contained in:
James
2024-02-15 11:04:52 -05:00
17 changed files with 286 additions and 302 deletions

4
.env.example Normal file
View File

@@ -0,0 +1,4 @@
DATABASE_URI=mongodb://127.0.0.1/payloadtests
PAYLOAD_SECRET=laijflieawfjlweifjewalifjwe
# PAYLOAD_CONFIG_PATH=MUST BE SET PROGRAMMATICALLY
PAYLOAD_DROP_DATABASE=true

View File

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

View File

@@ -1,3 +0,0 @@
html {
color: blue;
}

View File

@@ -1,12 +0,0 @@
import React from 'react'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{/* Layout UI */}
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -1,3 +0,0 @@
import './test.scss'
export default () => <h1>hello</h1>

View File

@@ -1,5 +0,0 @@
@import './another.scss';
html {
background: red;
}

View File

@@ -15,9 +15,6 @@ const customJestConfig = {
// testEnvironment: 'node', // testEnvironment: 'node',
testMatch: ['<rootDir>/packages/payload/src/**/*.spec.ts', '<rootDir>/test/**/*int.spec.ts'], testMatch: ['<rootDir>/packages/payload/src/**/*.spec.ts', '<rootDir>/test/**/*int.spec.ts'],
testTimeout: 90000, testTimeout: 90000,
// transform: {
// '^.+\\.(t|j)sx?$': ['@swc/jest'],
// },
verbose: true, verbose: true,
} }

View File

@@ -48,7 +48,7 @@ if (!cached) {
cached = global._payload_graphql = { graphql: null, promise: null } cached = global._payload_graphql = { graphql: null, promise: null }
} }
export const getGraphql = async (config: Promise<SanitizedConfig>) => { export const getGraphql = async (config: Promise<SanitizedConfig> | SanitizedConfig) => {
if (cached.graphql) { if (cached.graphql) {
return cached.graphql return cached.graphql
} }
@@ -71,53 +71,54 @@ export const getGraphql = async (config: Promise<SanitizedConfig>) => {
return cached.graphql return cached.graphql
} }
export const POST = (config: Promise<SanitizedConfig>) => async (request: Request) => { export const POST =
const originalRequest = request.clone() (config: Promise<SanitizedConfig> | SanitizedConfig) => async (request: Request) => {
const req = await createPayloadRequest({ const originalRequest = request.clone()
request, const req = await createPayloadRequest({
config, request,
}) config,
const { schema, validationRules } = await getGraphql(config) })
const { schema, validationRules } = await getGraphql(config)
const { payload } = req const { payload } = req
const afterErrorHook = const afterErrorHook =
typeof payload.config.hooks.afterError === 'function' ? payload.config.hooks.afterError : null typeof payload.config.hooks.afterError === 'function' ? payload.config.hooks.afterError : null
const headers = {} const headers = {}
const apiResponse = await createHandler({ const apiResponse = await createHandler({
context: { req, headers }, context: { req, headers },
onOperation: async (request, args, result) => { onOperation: async (request, args, result) => {
const response = const response =
typeof payload.extensions === 'function' typeof payload.extensions === 'function'
? await payload.extensions({ ? await payload.extensions({
args, args,
req: request, req: request,
result, result,
}) })
: result : result
if (response.errors) { if (response.errors) {
const errors = (await Promise.all( const errors = (await Promise.all(
result.errors.map((error) => { result.errors.map((error) => {
return handleError(payload, error, payload.config.debug, afterErrorHook) return handleError(payload, error, payload.config.debug, afterErrorHook)
}), }),
)) as GraphQLError[] )) as GraphQLError[]
// errors type should be FormattedGraphQLError[] but onOperation has a return type of ExecutionResult instead of FormattedExecutionResult // errors type should be FormattedGraphQLError[] but onOperation has a return type of ExecutionResult instead of FormattedExecutionResult
return { ...response, errors } return { ...response, errors }
} }
return response return response
}, },
schema: schema, schema: schema,
validationRules: (request, args, defaultRules) => defaultRules.concat(validationRules(args)), validationRules: (request, args, defaultRules) => defaultRules.concat(validationRules(args)),
})(originalRequest) })(originalRequest)
const resHeaders = new Headers(apiResponse.headers) const resHeaders = new Headers(apiResponse.headers)
for (let key in headers) { for (let key in headers) {
resHeaders.append(key, headers[key]) resHeaders.append(key, headers[key])
}
return new Response(apiResponse.body, {
status: apiResponse.status,
headers: new Headers(resHeaders),
})
} }
return new Response(apiResponse.body, {
status: apiResponse.status,
headers: new Headers(resHeaders),
})
}

View File

@@ -66,44 +66,42 @@ export const RouteError = async ({
err: APIError err: APIError
collection?: Collection collection?: Collection
}) => { }) => {
return Response.json(err, { status: 500 }) const { config, logger } = req.payload
let response = formatErrors(err)
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR
// const { config, logger } = req.payload logger.error(err.stack)
// let response = formatErrors(err)
// let status = err.status || httpStatus.INTERNAL_SERVER_ERROR
// logger.error(err.stack) // Internal server errors can contain anything, including potentially sensitive data.
// Therefore, error details will be hidden from the response unless `config.debug` is `true`
if (!config.debug && status === httpStatus.INTERNAL_SERVER_ERROR) {
response = formatErrors(new APIError('Something went wrong.'))
}
// // Internal server errors can contain anything, including potentially sensitive data. if (config.debug && config.debug === true) {
// // Therefore, error details will be hidden from the response unless `config.debug` is `true` response.stack = err.stack
// if (!config.debug && status === httpStatus.INTERNAL_SERVER_ERROR) { }
// response = formatErrors(new APIError('Something went wrong.'))
// }
// if (config.debug && config.debug === true) { if (collection && typeof collection.config.hooks.afterError === 'function') {
// response.stack = err.stack ;({ response, status } = (await collection.config.hooks.afterError(
// } err,
response,
req.context,
collection.config,
)) || { response, status })
}
// if (collection && typeof collection.config.hooks.afterError === 'function') { if (typeof config.hooks.afterError === 'function') {
// ;({ response, status } = (await collection.config.hooks.afterError( ;({ response, status } = (await config.hooks.afterError(
// err, err,
// response, response,
// req.context, req.context,
// collection.config, collection?.config,
// )) || { response, status }) )) || {
// } response,
status,
})
}
// if (typeof config.hooks.afterError === 'function') { return Response.json(response, { status })
// ;({ response, status } = (await config.hooks.afterError(
// err,
// response,
// req.context,
// collection?.config,
// )) || {
// response,
// status,
// })
// }
// return Response.json(response, { status })
} }

View File

@@ -145,7 +145,7 @@ const RouteNotFoundResponse = (slug: string[]) =>
) )
export const GET = export const GET =
(config: Promise<SanitizedConfig>) => (config: Promise<SanitizedConfig> | SanitizedConfig) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => { async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2, slug3, slug4] = slug const [slug1, slug2, slug3, slug4] = slug
let req: PayloadRequest let req: PayloadRequest
@@ -261,7 +261,7 @@ export const GET =
} }
export const POST = export const POST =
(config: Promise<SanitizedConfig>) => (config: Promise<SanitizedConfig> | SanitizedConfig) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => { async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2, slug3, slug4] = slug const [slug1, slug2, slug3, slug4] = slug
let req: PayloadRequest let req: PayloadRequest
@@ -370,7 +370,7 @@ export const POST =
} }
export const DELETE = export const DELETE =
(config: Promise<SanitizedConfig>) => (config: Promise<SanitizedConfig> | SanitizedConfig) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => { async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2] = slug const [slug1, slug2] = slug
let req: PayloadRequest let req: PayloadRequest
@@ -427,7 +427,7 @@ export const DELETE =
} }
export const PATCH = export const PATCH =
(config: Promise<SanitizedConfig>) => (config: Promise<SanitizedConfig> | SanitizedConfig) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => { async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2] = slug const [slug1, slug2] = slug
let req: PayloadRequest let req: PayloadRequest

View File

@@ -15,7 +15,7 @@ import { getDataAndFile } from './getDataAndFile'
type Args = { type Args = {
request: Request request: Request
config: Promise<SanitizedConfig> config: Promise<SanitizedConfig> | SanitizedConfig
params?: { params?: {
collection: string collection: string
} }

View File

@@ -6,10 +6,7 @@
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"esModuleInterop": true, "esModuleInterop": true,
"outDir": "./dist" /* Specify an output folder for all emitted files. */, "outDir": "./dist" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */, "rootDir": "./src" /* Specify the root folder within your source files. */
"paths": {
"payload-config": ["./src/config.ts"]
}
}, },
"exclude": [ "exclude": [
"dist", "dist",

View File

@@ -1,20 +1,15 @@
import type { Payload } from '../../packages/payload/src' import type { Payload } from '../../packages/payload/src'
import { GET as createGET, POST as createPOST } from '../../packages/next/src/routes/rest/index'
import { getPayload } from '../../packages/payload/src' import { getPayload } from '../../packages/payload/src'
import { devUser } from '../credentials' import { devUser } from '../credentials'
import { NextRESTClient } from '../helpers/NextRESTClient'
import { postsSlug } from './collections/Posts' import { postsSlug } from './collections/Posts'
import config from './config' import configPromise from './config'
let payload: Payload let payload: Payload
let jwt let token: string
let restClient: NextRESTClient
const GET = createGET(config)
const POST = createPOST(config)
const headers = {
'Content-Type': 'application/json',
}
const { email, password } = devUser const { email, password } = devUser
describe('_Community Tests', () => { describe('_Community Tests', () => {
@@ -22,28 +17,19 @@ describe('_Community Tests', () => {
// Boilerplate test setup/teardown // Boilerplate test setup/teardown
// --__--__--__--__--__--__--__--__--__ // --__--__--__--__--__--__--__--__--__
beforeAll(async () => { beforeAll(async () => {
payload = await getPayload({ config }) payload = await getPayload({ config: configPromise })
restClient = new NextRESTClient(payload.config)
const req = new Request('http://localhost:3000/api/users/login', { const data = await restClient
method: 'POST', .POST('/users/login', {
headers: new Headers(headers), body: JSON.stringify({
body: JSON.stringify({ email,
email, password,
password, }),
}), })
}) .then((res) => res.json())
const data = await POST(req, { token = data.token
params: {
slug: ['users', 'login'],
},
}).then((res) => res.json())
jwt = data.token
})
beforeEach(() => {
jest.resetModules()
}) })
afterAll(async () => { afterAll(async () => {
@@ -69,22 +55,16 @@ describe('_Community Tests', () => {
}) })
it('rest API example', async () => { it('rest API example', async () => {
const req = new Request(`http://localhost:3000/posts`, { const data = await restClient
method: 'POST', .POST(`/${postsSlug}`, {
headers: new Headers({ body: JSON.stringify({
...headers, text: 'REST API EXAMPLE',
Authorization: `JWT ${jwt}`, }),
}), headers: {
body: JSON.stringify({ Authorization: `JWT ${token}`,
text: 'REST API EXAMPLE', },
}), })
}) .then((res) => res.json())
const data = await POST(req, {
params: {
slug: ['posts'],
},
}).then((res) => res.json())
expect(data.doc.text).toEqual('REST API EXAMPLE') expect(data.doc.text).toEqual('REST API EXAMPLE')
}) })

View File

@@ -158,16 +158,16 @@ export default buildConfigWithDefaults({
label: 'Custom', label: 'Custom',
type: 'text', type: 'text',
}, },
{ // {
name: 'authDebug', // name: 'authDebug',
label: 'Auth Debug', // label: 'Auth Debug',
type: 'ui', // type: 'ui',
admin: { // admin: {
components: { // components: {
Field: AuthDebug, // Field: AuthDebug,
}, // },
}, // },
}, // },
], ],
}, },
{ {

View File

@@ -1,37 +1,24 @@
import { GraphQLClient } from 'graphql-request'
import jwtDecode from 'jwt-decode' import jwtDecode from 'jwt-decode'
import type { Payload } from '../../packages/payload/src' import type { Payload } from '../../packages/payload/src'
import type { User } from '../../packages/payload/src/auth' import type { User } from '../../packages/payload/src/auth'
import configPromise from '../collections-graphql/config' import { getPayload } from '../../packages/payload/src'
import { devUser } from '../credentials' import { devUser } from '../credentials'
import { initPayloadTest } from '../helpers/configHelpers' import { NextRESTClient } from '../helpers/NextRESTClient'
import configPromise from './config'
import { namedSaveToJWTValue, saveToJWTKey, slug } from './shared' import { namedSaveToJWTValue, saveToJWTKey, slug } from './shared'
require('isomorphic-fetch')
let apiUrl let apiUrl
let client: GraphQLClient let restClient: NextRESTClient
let payload: Payload let payload: Payload
const headers = {
'Content-Type': 'application/json',
}
const { email, password } = devUser const { email, password } = devUser
describe('Auth', () => { describe('Auth', () => {
beforeAll(async () => { beforeAll(async () => {
const { serverURL, payload: payloadClient } = await initPayloadTest({ payload = await getPayload({ config: configPromise, disableOnInit: true })
__dirname, restClient = new NextRESTClient(payload.config)
init: { local: false },
})
payload = payloadClient
apiUrl = `${serverURL}/api`
const config = await configPromise
const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`
client = new GraphQLClient(url)
}) })
afterAll(async () => { afterAll(async () => {
@@ -40,27 +27,28 @@ describe('Auth', () => {
} }
}) })
beforeEach(() => {
jest.resetModules()
})
describe('GraphQL - admin user', () => { describe('GraphQL - admin user', () => {
let token let token
let user let user
beforeAll(async () => { beforeAll(async () => {
// language=graphQL const { data } = await restClient
const query = `mutation { .GRAPHQL_POST({
loginUser(email: "${devUser.email}", password: "${devUser.password}") { body: JSON.stringify({
query: `mutation {
loginUser(email: "${devUser.email}", password: "${devUser.password}") {
token token
user { user {
id id
email email
} }
} }
}` }`,
const response = await client.request(query) }),
user = response.loginUser.user })
token = response.loginUser.token .then((res) => res.json())
user = data.loginUser.user
token = data.loginUser.token
}) })
it('should login', async () => { it('should login', async () => {
@@ -83,37 +71,31 @@ describe('Auth', () => {
describe('REST - admin user', () => { describe('REST - admin user', () => {
beforeAll(async () => { beforeAll(async () => {
await fetch(`${apiUrl}/${slug}/first-register`, { await restClient.POST(`/${slug}/first-register`, {
body: JSON.stringify({ body: JSON.stringify({
email, email,
password, password,
}), }),
headers,
method: 'post',
}) })
}) })
it('should prevent registering a new first user', async () => { it('should prevent registering a new first user', async () => {
const response = await fetch(`${apiUrl}/${slug}/first-register`, { const response = await restClient.POST(`/${slug}/first-register`, {
body: JSON.stringify({ body: JSON.stringify({
email: 'thisuser@shouldbeprevented.com', email,
password: 'get-out', password,
}), }),
headers,
method: 'post',
}) })
expect(response.status).toBe(403) expect(response.status).toBe(403)
}) })
it('should login a user successfully', async () => { it('should login a user successfully', async () => {
const response = await fetch(`${apiUrl}/${slug}/login`, { const response = await restClient.POST(`/${slug}/login`, {
body: JSON.stringify({ body: JSON.stringify({
email, email,
password, password,
}), }),
headers,
method: 'post',
}) })
const data = await response.json() const data = await response.json()
@@ -127,13 +109,11 @@ describe('Auth', () => {
let loggedInUser: User | undefined let loggedInUser: User | undefined
beforeAll(async () => { beforeAll(async () => {
const response = await fetch(`${apiUrl}/${slug}/login`, { const response = await restClient.POST(`/${slug}/login`, {
body: JSON.stringify({ body: JSON.stringify({
email, email,
password, password,
}), }),
headers,
method: 'post',
}) })
const data = await response.json() const data = await response.json()
@@ -155,9 +135,8 @@ describe('Auth', () => {
}) })
it('should return a logged in user from /me', async () => { it('should return a logged in user from /me', async () => {
const response = await fetch(`${apiUrl}/${slug}/me`, { const response = await restClient.GET(`/${slug}/me`, {
headers: { headers: {
...headers,
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
}, },
}) })
@@ -218,9 +197,8 @@ describe('Auth', () => {
}, },
}) })
const response = await fetch(`${apiUrl}/${slug}/me`, { const response = await restClient.GET(`/${slug}/me`, {
headers: { headers: {
...headers,
Authorization: `${slug} API-Key ${user?.apiKey}`, Authorization: `${slug} API-Key ${user?.apiKey}`,
}, },
}) })
@@ -233,11 +211,10 @@ describe('Auth', () => {
}) })
it('should refresh a token and reset its expiration', async () => { it('should refresh a token and reset its expiration', async () => {
const response = await fetch(`${apiUrl}/${slug}/refresh-token`, { const response = await restClient.POST(`/${slug}/refresh-token`, {
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
}, },
method: 'post',
}) })
const data = await response.json() const data = await response.json()
@@ -257,11 +234,10 @@ describe('Auth', () => {
}, },
}) })
const response = await fetch(`${apiUrl}/${slug}/refresh-token`, { const response = await restClient.POST(`/${slug}/refresh-token`, {
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
}, },
method: 'post',
}) })
const data = await response.json() const data = await response.json()
@@ -271,7 +247,7 @@ describe('Auth', () => {
}) })
it('should allow a user to be created', async () => { it('should allow a user to be created', async () => {
const response = await fetch(`${apiUrl}/${slug}`, { const response = await restClient.POST(`/${slug}`, {
body: JSON.stringify({ body: JSON.stringify({
email: 'name@test.com', email: 'name@test.com',
password, password,
@@ -279,9 +255,7 @@ describe('Auth', () => {
}), }),
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'post',
}) })
const data = await response.json() const data = await response.json()
@@ -299,7 +273,7 @@ describe('Auth', () => {
it('should allow verification of a user', async () => { it('should allow verification of a user', async () => {
const emailToVerify = 'verify@me.com' const emailToVerify = 'verify@me.com'
const response = await fetch(`${apiUrl}/public-users`, { const response = await restClient.POST(`/public-users`, {
body: JSON.stringify({ body: JSON.stringify({
email: emailToVerify, email: emailToVerify,
password, password,
@@ -307,9 +281,7 @@ describe('Auth', () => {
}), }),
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'post',
}) })
expect(response.status).toBe(201) expect(response.status).toBe(201)
@@ -330,14 +302,8 @@ describe('Auth', () => {
expect(_verified).toBe(false) expect(_verified).toBe(false)
expect(_verificationToken).toBeDefined() expect(_verificationToken).toBeDefined()
const verificationResponse = await fetch( const verificationResponse = await restClient.POST(
`${apiUrl}/public-users/verify/${_verificationToken}`, `/public-users/verify/${_verificationToken}`,
{
headers: {
'Content-Type': 'application/json',
},
method: 'post',
},
) )
expect(verificationResponse.status).toBe(200) expect(verificationResponse.status).toBe(200)
@@ -365,15 +331,13 @@ describe('Auth', () => {
let data let data
beforeAll(async () => { beforeAll(async () => {
const response = await fetch(`${apiUrl}/payload-preferences/${key}`, { const response = await restClient.POST(`/${slug}/payload-preferences/${key}`, {
body: JSON.stringify({ body: JSON.stringify({
value: { property }, value: { property },
}), }),
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'post',
}) })
data = await response.json() data = await response.json()
}) })
@@ -384,12 +348,10 @@ describe('Auth', () => {
}) })
it('should read', async () => { it('should read', async () => {
const response = await fetch(`${apiUrl}/payload-preferences/${key}`, { const response = await restClient.GET(`/${slug}/payload-preferences/${key}`, {
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'get',
}) })
data = await response.json() data = await response.json()
expect(data.key).toStrictEqual(key) expect(data.key).toStrictEqual(key)
@@ -397,15 +359,13 @@ describe('Auth', () => {
}) })
it('should update', async () => { it('should update', async () => {
const response = await fetch(`${apiUrl}/payload-preferences/${key}`, { const response = await restClient.POST(`/${slug}/payload-preferences/${key}`, {
body: JSON.stringify({ body: JSON.stringify({
value: { property: 'updated', property2: 'test' }, value: { property: 'updated', property2: 'test' },
}), }),
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'post',
}) })
data = await response.json() data = await response.json()
@@ -426,14 +386,11 @@ describe('Auth', () => {
}) })
it('should delete', async () => { it('should delete', async () => {
const response = await fetch(`${apiUrl}/payload-preferences/${key}`, { const response = await restClient.DELETE(`/${slug}/payload-preferences/${key}`, {
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'delete',
}) })
data = await response.json() data = await response.json()
const result = await payload.find({ const result = await payload.find({
@@ -452,43 +409,34 @@ describe('Auth', () => {
const userEmail = 'lock@me.com' const userEmail = 'lock@me.com'
const tryLogin = async () => { const tryLogin = async () => {
await fetch(`${apiUrl}/${slug}/login`, { await restClient.POST(`/${slug}/login`, {
body: JSON.stringify({ body: JSON.stringify({
email: userEmail, email: userEmail,
password: 'bad', password: 'bad',
}), }),
headers: {
'Content-Type': 'application/json',
},
method: 'post',
}) })
// expect(loginRes.status).toEqual(401);
} }
beforeAll(async () => { beforeAll(async () => {
const response = await fetch(`${apiUrl}/${slug}/login`, { const response = await restClient.POST(`/${slug}/login`, {
body: JSON.stringify({ body: JSON.stringify({
email, email,
password, password,
}), }),
headers,
method: 'post',
}) })
const data = await response.json() const data = await response.json()
token = data.token token = data.token
// New user to lock // New user to lock
await fetch(`${apiUrl}/${slug}`, { await restClient.POST(`/${slug}`, {
body: JSON.stringify({ body: JSON.stringify({
email: userEmail, email: userEmail,
password, password,
}), }),
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'post',
}) })
}) })
@@ -531,16 +479,14 @@ describe('Auth', () => {
}) })
// login // login
await fetch(`${apiUrl}/${slug}/login`, { await restClient.POST(`/${slug}/login`, {
body: JSON.stringify({ body: JSON.stringify({
email: userEmail, email: userEmail,
password, password,
}), }),
headers: { headers: {
Authorization: `JWT ${token}`, Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
}, },
method: 'post',
}) })
const userResult = await payload.find({ const userResult = await payload.find({
@@ -564,16 +510,11 @@ describe('Auth', () => {
it('should allow forgot-password by email', async () => { it('should allow forgot-password by email', async () => {
// TODO: Spy on payload sendEmail function // TODO: Spy on payload sendEmail function
const response = await fetch(`${apiUrl}/${slug}/forgot-password`, { const response = await restClient.POST(`/${slug}/forgot-password`, {
body: JSON.stringify({ body: JSON.stringify({
email, email,
}), }),
headers: {
'Content-Type': 'application/json',
},
method: 'post',
}) })
// expect(mailSpy).toHaveBeenCalled(); // expect(mailSpy).toHaveBeenCalled();
expect(response.status).toBe(200) expect(response.status).toBe(200)
@@ -613,21 +554,22 @@ describe('Auth', () => {
}, },
}) })
const response = await fetch(`${apiUrl}/${slug}/login`, { const response = await restClient.POST(`/${slug}/login`, {
body: JSON.stringify({ body: JSON.stringify({
email: 'insecure@me.com', email: 'insecure@me.com',
password: 'test', password: 'test',
}), }),
headers,
method: 'post',
}) })
const data = await response.json() const data = await response.json()
const adminMe = await fetch(`${apiUrl}/${slug}/me`, { const adminMe = await restClient
headers: { .GET(`/${slug}/me`, {
Authorization: `JWT ${data.token}`, headers: {
}, Authorization: `JWT ${data.token}`,
}).then((res) => res.json()) },
})
.then((res) => res.json())
expect(adminMe.user.adminOnlyField).toEqual('admin secret') expect(adminMe.user.adminOnlyField).toEqual('admin secret')
await payload.update({ await payload.update({
@@ -638,21 +580,21 @@ describe('Auth', () => {
}, },
}) })
const editorMe = await fetch(`${apiUrl}/${slug}/me`, { const editorMe = await restClient
headers: { .GET(`/${slug}/me`, {
Authorization: `JWT ${adminMe?.token}`, headers: {
}, Authorization: `JWT ${data.token}`,
}).then((res) => res.json()) },
})
.then((res) => res.json())
expect(editorMe.user.adminOnlyField).toBeUndefined() expect(editorMe.user.adminOnlyField).toBeUndefined()
}) })
it('should not allow refreshing an invalid token', async () => { it('should not allow refreshing an invalid token', async () => {
const response = await fetch(`${apiUrl}/${slug}/refresh-token`, { const response = await restClient.POST(`/${slug}/refresh-token`, {
body: JSON.stringify({ body: JSON.stringify({
token: 'INVALID', token: 'INVALID',
}), }),
headers,
method: 'post',
}) })
const data = await response.json() const data = await response.json()
@@ -678,19 +620,19 @@ describe('Auth', () => {
const [user1, user2] = usersQuery.docs const [user1, user2] = usersQuery.docs
const success = await fetch(`${apiUrl}/api-keys/${user2.id}`, { const success = await restClient
headers: { .GET(`/api-keys/${user2.id}`, {
Authorization: `api-keys API-Key ${user2.apiKey}`, headers: {
'Content-Type': 'application/json', Authorization: `api-keys API-Key ${user2.apiKey}`,
}, },
}).then((res) => res.json()) })
.then((res) => res.json())
expect(success.apiKey).toStrictEqual(user2.apiKey) expect(success.apiKey).toStrictEqual(user2.apiKey)
const fail = await fetch(`${apiUrl}/api-keys/${user1.id}`, { const fail = await restClient.GET(`/api-keys/${user1.id}`, {
headers: { headers: {
Authorization: `api-keys API-Key ${user2.apiKey}`, Authorization: `api-keys API-Key ${user2.apiKey}`,
'Content-Type': 'application/json',
}, },
}) })

View File

@@ -0,0 +1,94 @@
import type { SanitizedConfig } from '../../packages/payload/types'
import { GRAPHQL_POST as createGraphqlPOST } from '../../packages/next/src/routes/graphql'
import {
DELETE as createDELETE,
GET as createGET,
PATCH as createPATCH,
POST as createPOST,
} from '../../packages/next/src/routes/rest'
type ValidPath = `/${string}`
export class NextRESTClient {
private _DELETE: (request: Request, args: { params: { slug: string[] } }) => Promise<Response>
private _GET: (request: Request, args: { params: { slug: string[] } }) => Promise<Response>
private _GRAPHQL_POST: (request: Request) => Promise<Response>
private _PATCH: (request: Request, args: { params: { slug: string[] } }) => Promise<Response>
private _POST: (request: Request, args: { params: { slug: string[] } }) => Promise<Response>
private readonly config: SanitizedConfig
serverURL: string = 'http://localhost:3000'
constructor(config: SanitizedConfig) {
this.config = config
if (config?.serverURL) this.serverURL = config.serverURL
this._GET = createGET(config)
this._POST = createPOST(config)
this._DELETE = createDELETE(config)
this._PATCH = createPATCH(config)
this._GRAPHQL_POST = createGraphqlPOST(config)
}
private generateRequestParts(path: string): {
slug: string[]
url: string
} {
const safePath = path.slice(1)
const slug = safePath.split('/')
const url = `${this.serverURL}${this.config.routes.api}/${safePath}`
return {
url,
slug,
}
}
async DELETE(path: ValidPath, options: RequestInit): Promise<Response> {
const { url, slug } = this.generateRequestParts(path)
const request = new Request(url, { ...options, method: 'DELETE' })
return this._DELETE(request, { params: { slug } })
}
async GET(path: ValidPath, options?: Omit<RequestInit, 'body'>): Promise<Response> {
const { url, slug } = this.generateRequestParts(path)
const request = new Request(url, { ...options, method: 'GET' })
return this._GET(request, { params: { slug } })
}
async GRAPHQL_POST(options: RequestInit): Promise<Response> {
const request = new Request(`${this.serverURL}${this.config.routes.graphQL}`, {
...options,
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json',
...(options?.headers || {}),
}),
})
return this._GRAPHQL_POST(request)
}
async PATCH(path: ValidPath, options: RequestInit): Promise<Response> {
const { url, slug } = this.generateRequestParts(path)
const request = new Request(url, { ...options, method: 'PATCH' })
return this._PATCH(request, { params: { slug } })
}
async POST(path: ValidPath, options?: RequestInit): Promise<Response> {
const { url, slug } = this.generateRequestParts(path)
const request = new Request(url, {
...options,
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json',
...(options?.headers || {}),
}),
})
return this._POST(request, { params: { slug } })
}
}

View File

@@ -34,14 +34,11 @@
"@payloadcms/db-mongodb": ["./packages/db-mongodb/src"], "@payloadcms/db-mongodb": ["./packages/db-mongodb/src"],
"@payloadcms/richtext-lexical": ["./packages/richtext-lexical/src"], "@payloadcms/richtext-lexical": ["./packages/richtext-lexical/src"],
"@payloadcms/ui/*": ["./packages/ui/src/exports/*"], "@payloadcms/ui/*": ["./packages/ui/src/exports/*"],
"@payloadcms/ui/scss": ["./packages/ui/src/scss/styles.scss"],
"@payloadcms/ui/scss/app.scss": ["./packages/ui/src/scss/app.scss"],
"@payloadcms/translations": ["./packages/translations/src/exports/index.ts"], "@payloadcms/translations": ["./packages/translations/src/exports/index.ts"],
"@payloadcms/translations/client": ["./packages/translations/src/all"], "@payloadcms/translations/client": ["./packages/translations/src/all"],
"@payloadcms/translations/api": ["./packages/translations/src/all"], "@payloadcms/translations/api": ["./packages/translations/src/all"],
"@payloadcms/next/*": ["./packages/next/src/*"], "@payloadcms/next/*": ["./packages/next/src/*"],
"@payloadcms/graphql": ["./packages/graphql/src"], "@payloadcms/graphql": ["./packages/graphql/src"]
"payload-config": ["./test/_community/config.ts"]
} }
}, },
"exclude": ["dist", "build", "temp", "node_modules"], "exclude": ["dist", "build", "temp", "node_modules"],