Fix/alpha/int tests (#5311)

* chore: converts dynamic imports to esm require

* chore: adjusts require and import usage

* chore: reverts bin script change

* chore: adjust dataloaded tests to use slate editor

* fix: converts custom auth strategy

* chore: fixes to form builder int test config

* chore: adjusts plugin-stripe and int tests

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
This commit is contained in:
Dan Ribbens
2024-03-12 16:27:43 -04:00
committed by GitHub
parent 8436dd5851
commit 04fcf57d0a
18 changed files with 202 additions and 253 deletions

View File

@@ -1,32 +0,0 @@
import type { Config } from 'payload/config'
import type { Configuration as WebpackConfig } from 'webpack'
import path from 'path'
export const extendWebpackConfig =
(config: Config): ((webpackConfig: WebpackConfig) => WebpackConfig) =>
(webpackConfig) => {
const existingWebpackConfig =
typeof config.admin?.webpack === 'function'
? config.admin.webpack(webpackConfig)
: webpackConfig
const mockModulePath = path.resolve(__dirname, './mocks/mockFile.js')
return {
...existingWebpackConfig,
resolve: {
...(existingWebpackConfig.resolve || {}),
alias: {
...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
'@payloadcms/plugin-stripe': path.resolve(__dirname, './admin.js'),
express: mockModulePath,
[path.resolve(__dirname, './hooks/createNewInStripe')]: mockModulePath,
[path.resolve(__dirname, './hooks/deleteFromStripe')]: mockModulePath,
[path.resolve(__dirname, './hooks/syncExistingWithStripe')]: mockModulePath,
[path.resolve(__dirname, './routes/rest')]: mockModulePath,
[path.resolve(__dirname, './routes/webhooks')]: mockModulePath,
},
},
}
}

View File

@@ -1,18 +1,13 @@
import type { NextFunction, Response } from 'express'
import type { Config, Endpoint } from 'payload/config'
import type { PayloadRequest } from 'payload/types'
import express from 'express'
import type { SanitizedStripeConfig, StripeConfig } from './types.js'
import type { SanitizedStripeConfig, StripeConfig } from './types'
import { extendWebpackConfig } from './extendWebpackConfig'
import { getFields } from './fields/getFields'
import { createNewInStripe } from './hooks/createNewInStripe'
import { deleteFromStripe } from './hooks/deleteFromStripe'
import { syncExistingWithStripe } from './hooks/syncExistingWithStripe'
import { stripeREST } from './routes/rest'
import { stripeWebhooks } from './routes/webhooks'
import { getFields } from './fields/getFields.js'
import { createNewInStripe } from './hooks/createNewInStripe.js'
import { deleteFromStripe } from './hooks/deleteFromStripe.js'
import { syncExistingWithStripe } from './hooks/syncExistingWithStripe.js'
import { stripeREST } from './routes/rest.js'
import { stripeWebhooks } from './routes/webhooks.js'
const stripePlugin =
(incomingStripeConfig: StripeConfig) =>
@@ -31,12 +26,40 @@ const stripePlugin =
// unfortunately we must set the 'isTestKey' property on the config instead of using the following code:
// const isTestKey = stripeConfig.stripeSecretKey?.startsWith('sk_test_');
const endpoints: Endpoint[] = [
...(config?.endpoints || []),
{
handler: async (req) => {
const res = await stripeWebhooks({
config,
req,
stripeConfig,
})
return res
},
method: 'post',
path: '/stripe/webhooks',
},
]
if (incomingStripeConfig?.rest) {
endpoints.push({
handler: async (req) => {
const res = await stripeREST({
req,
stripeConfig,
})
return res
},
method: 'post' as Endpoint['method'],
path: '/stripe/rest',
})
}
return {
...config,
admin: {
...config.admin,
webpack: extendWebpackConfig(config),
},
collections: collections?.map((collection) => {
const { hooks: existingHooks } = collection
@@ -86,42 +109,7 @@ const stripePlugin =
return collection
}),
endpoints: [
...(config?.endpoints || []),
{
handler: [
express.raw({ type: 'application/json' }),
async (req, res, next) => {
await stripeWebhooks({
config,
next,
req,
res,
stripeConfig,
})
},
],
method: 'post',
path: '/stripe/webhooks',
root: true,
},
...(incomingStripeConfig?.rest
? [
{
handler: async (req: PayloadRequest, res: Response, next: NextFunction) => {
await stripeREST({
next,
req,
res,
stripeConfig,
})
},
method: 'post' as Endpoint['method'],
path: '/stripe/rest',
},
]
: []),
],
endpoints,
}
}

View File

@@ -1,22 +1,22 @@
import type { Response } from 'express'
import type { PayloadRequest } from 'payload/types'
import { Forbidden } from 'payload/errors'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { stripeProxy } from '../utilities/stripeProxy'
import { stripeProxy } from '../utilities/stripeProxy.js'
export const stripeREST = async (args: {
next: any
req: PayloadRequest
res: Response
stripeConfig: StripeConfig
}): Promise<any> => {
const { req, res, stripeConfig } = args
let responseStatus = 200
let responseJSON
const { req, stripeConfig } = args
const {
body: {
data: {
stripeArgs, // example: ['cus_MGgt3Tuj3D66f2'] or [{ limit: 100 }, { stripeAccount: 'acct_1J9Z4pKZ4Z4Z4Z4Z' }]
stripeMethod, // example: 'subscriptions.list',
},
@@ -32,20 +32,26 @@ export const stripeREST = async (args: {
throw new Forbidden(req.t)
}
const pluginRes = await stripeProxy({
responseJSON = await stripeProxy({
stripeArgs,
stripeMethod,
stripeSecretKey,
})
const { status } = pluginRes
res.status(status).json(pluginRes)
const { status } = responseJSON
responseStatus = status
} catch (error: unknown) {
const message = `An error has occurred in the Stripe plugin REST handler: '${error}'`
const message = `An error has occurred in the Stripe plugin REST handler: '${JSON.stringify(
error,
)}'`
payload.logger.error(message)
return res.status(500).json({
responseStatus = 500
responseJSON = {
message,
})
}
}
return Response.json(responseJSON, {
status: responseStatus,
})
}

View File

@@ -1,21 +1,19 @@
import type { Response } from 'express'
import type { Config as PayloadConfig } from 'payload/config'
import type { PayloadRequest } from 'payload/types'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { handleWebhooks } from '../webhooks'
import { handleWebhooks } from '../webhooks/index.js'
export const stripeWebhooks = async (args: {
config: PayloadConfig
next: any
req: PayloadRequest
res: Response
stripeConfig: StripeConfig
}): Promise<any> => {
const { config, req, res, stripeConfig } = args
const { config, req, stripeConfig } = args
let returnStatus = 200
const { stripeSecretKey, stripeWebhooksEndpointSecret, webhooks } = stripeConfig
@@ -28,21 +26,21 @@ export const stripeWebhooks = async (args: {
},
})
const stripeSignature = req.headers['stripe-signature']
const stripeSignature = req.headers.get('stripe-signature')
if (stripeSignature) {
let event: Stripe.Event | undefined
try {
event = stripe.webhooks.constructEvent(
req.body,
await req.text(),
stripeSignature,
stripeWebhooksEndpointSecret,
)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : err
const msg: string = err instanceof Error ? err.message : JSON.stringify(err)
req.payload.logger.error(`Error constructing Stripe event: ${msg}`)
res.status(400)
returnStatus = 400
}
if (event) {
@@ -81,5 +79,10 @@ export const stripeWebhooks = async (args: {
}
}
res.json({ received: true })
return Response.json(
{ received: true },
{
status: returnStatus,
},
)
}

View File

@@ -1,46 +1,30 @@
import { Strategy } from 'passport-strategy'
import type { Payload } from '../../../packages/payload/src/index.js'
import type { AuthStrategyFunction } from 'payload/auth.js'
import { buildConfigWithDefaults } from '../../buildConfigWithDefaults.js'
import { usersSlug } from './shared.js'
export const strategyName = 'test-local'
export class CustomStrategy extends Strategy {
ctx: Payload
const customAuthenticationStrategy: AuthStrategyFunction = async ({ headers, payload }) => {
const usersQuery = await payload.find({
collection: usersSlug,
where: {
code: {
equals: headers.get('code'),
},
secret: {
equals: headers.get('secret'),
},
},
})
constructor(ctx: Payload) {
super()
this.ctx = ctx
}
const user = usersQuery.docs[0] || null
if (!user) return null
authenticate(req: Request, options?: any): void {
if (!req.headers.code && !req.headers.secret) {
return this.success(null)
}
this.ctx
.find({
collection: usersSlug,
where: {
code: {
equals: req.headers.code,
},
secret: {
equals: req.headers.secret,
},
},
})
.then((users) => {
if (users.docs && users.docs.length) {
const user = users.docs[0]
user.collection = usersSlug
user._strategy = `${usersSlug}-${strategyName}`
this.success(user)
} else {
this.error(null)
}
})
return {
...user,
_strategy: `${usersSlug}-${strategyName}`,
collection: usersSlug,
}
}
@@ -51,45 +35,45 @@ export default buildConfigWithDefaults({
collections: [
{
slug: usersSlug,
access: {
create: () => true,
},
auth: {
disableLocalStrategy: true,
strategies: [
{
name: strategyName,
strategy: (ctx) => new CustomStrategy(ctx),
authenticate: customAuthenticationStrategy,
},
],
},
access: {
create: () => true,
},
fields: [
{
name: 'code',
label: 'Code',
type: 'text',
unique: true,
index: true,
label: 'Code',
unique: true,
},
{
name: 'secret',
label: 'Secret',
type: 'text',
label: 'Secret',
},
{
name: 'name',
label: 'Name',
type: 'text',
label: 'Name',
},
{
name: 'roles',
label: 'Role',
type: 'select',
options: ['admin', 'editor', 'moderator', 'user', 'viewer'],
defaultValue: 'user',
hasMany: true,
label: 'Role',
options: ['admin', 'editor', 'moderator', 'user', 'viewer'],
required: true,
saveToJWT: true,
hasMany: true,
},
],
},

View File

@@ -28,7 +28,7 @@ import {
UploadFeature,
lexicalEditor,
} from '../packages/richtext-lexical/src/index.js'
//import { slateEditor } from '../packages/richtext-slate/src/index.js'
// import { slateEditor } from '../packages/richtext-slate/src/index.js'
// process.env.PAYLOAD_DATABASE = 'postgres'
const databaseAdapters = {

View File

@@ -1,3 +1,4 @@
import { slateEditor } from '../../packages/richtext-slate/src/index.js'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
@@ -14,19 +15,15 @@ export default buildConfigWithDefaults({
{
name: 'owner',
type: 'relationship',
relationTo: 'users',
hooks: {
beforeChange: [({ req: { user } }) => user?.id],
},
relationTo: 'users',
},
],
},
{
slug: 'relation-a',
labels: {
singular: 'Relation A',
plural: 'Relation As',
},
fields: [
{
name: 'relationship',
@@ -36,15 +33,16 @@ export default buildConfigWithDefaults({
{
name: 'richText',
type: 'richText',
editor: slateEditor({}),
},
],
labels: {
plural: 'Relation As',
singular: 'Relation A',
},
},
{
slug: 'relation-b',
labels: {
singular: 'Relation B',
plural: 'Relation Bs',
},
fields: [
{
name: 'relationship',
@@ -54,8 +52,13 @@ export default buildConfigWithDefaults({
{
name: 'richText',
type: 'richText',
editor: slateEditor({}),
},
],
labels: {
plural: 'Relation Bs',
singular: 'Relation B',
},
},
],
onInit: async (payload) => {
@@ -68,9 +71,9 @@ export default buildConfigWithDefaults({
})
await payload.create({
user,
collection: 'posts',
data: postDoc,
user,
})
},
})

View File

@@ -1,6 +1,7 @@
import type { Block } from '../../packages/payload/src/fields/config/types.js'
import formBuilder, { fields as formFields } from '../../packages/plugin-form-builder/src/index.js'
import { slateEditor } from '../../packages/richtext-slate/src/index.js'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import { Pages } from './collections/Pages.js'
@@ -9,20 +10,21 @@ import { seed } from './seed/index.js'
const colorField: Block = {
slug: 'color',
labels: {
singular: 'Color',
plural: 'Colors',
},
fields: [
{
name: 'value',
type: 'text',
},
],
labels: {
plural: 'Colors',
singular: 'Color',
},
}
export default buildConfigWithDefaults({
collections: [Pages, Users],
editor: slateEditor({}),
localization: {
defaultLocale: 'en',
fallback: true,
@@ -43,27 +45,14 @@ export default buildConfigWithDefaults({
formBuilder({
// handlePayment: handleFormPayments,
// beforeEmail: prepareFormEmails,
redirectRelationships: ['pages'],
formOverrides: {
// labels: {
// singular: 'Contact Form',
// plural: 'Contact Forms'
// },
fields: [
{
name: 'custom',
type: 'text',
},
],
},
fields: {
payment: true,
colorField,
payment: true,
text: {
...formFields.text,
labels: {
singular: 'Custom Text Field',
plural: 'Custom Text Fields',
singular: 'Custom Text Field',
},
},
// payment: {
@@ -78,6 +67,19 @@ export default buildConfigWithDefaults({
// },
// },
},
formOverrides: {
// labels: {
// singular: 'Contact Form',
// plural: 'Contact Forms'
// },
fields: [
{
name: 'custom',
type: 'text',
},
],
},
redirectRelationships: ['pages'],
}),
],
})

View File

@@ -18,19 +18,19 @@ describe('@payloadcms/plugin-form-builder', () => {
payload = await getPayload({ config })
const formConfig: Omit<Form, 'createdAt' | 'id' | 'updatedAt'> = {
title: 'Test Form',
fields: [
{
name: 'name',
blockType: 'text',
},
],
confirmationMessage: [
{
type: 'text',
text: 'Confirmed.',
},
],
fields: [
{
name: 'name',
blockType: 'text',
},
],
title: 'Test Form',
}
form = (await payload.create({
@@ -54,19 +54,19 @@ describe('@payloadcms/plugin-form-builder', () => {
describe('form building', () => {
it('can create a simple form', async () => {
const formConfig: Omit<Form, 'createdAt' | 'id' | 'updatedAt'> = {
title: 'Test Form',
fields: [
{
name: 'name',
blockType: 'text',
},
],
confirmationMessage: [
{
type: 'text',
text: 'Confirmed.',
},
],
fields: [
{
name: 'name',
blockType: 'text',
},
],
title: 'Test Form',
}
const testForm = await payload.create({
@@ -81,14 +81,14 @@ describe('@payloadcms/plugin-form-builder', () => {
it('can use form overrides', async () => {
const formConfig: Omit<Form, 'createdAt' | 'id' | 'updatedAt'> = {
custom: 'custom',
title: 'Test Form',
confirmationMessage: [
{
type: 'text',
text: 'Confirmed.',
},
],
custom: 'custom',
title: 'Test Form',
}
const testForm = await payload.create({
@@ -142,7 +142,7 @@ describe('@payloadcms/plugin-form-builder', () => {
await expect(req).rejects.toThrow(ValidationError)
})
it('replaces curly braces with data when using slate serializer', async () => {
it('replaces curly braces with data when using slate serializer', () => {
const mockName = 'Test Submission'
const mockEmail = 'dev@payloadcms.com'
@@ -150,12 +150,12 @@ describe('@payloadcms/plugin-form-builder', () => {
[
{ text: 'Welcome {{name}}. Here is a dynamic ' },
{
type: 'link',
children: [
{
text: 'link',
},
],
type: 'link',
url: 'www.test.com?email={{email}}',
},
],
@@ -177,24 +177,17 @@ describe('@payloadcms/plugin-form-builder', () => {
{
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'paragraph',
direction: 'ltr',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Name: {{name}}',
type: 'text',
version: 1,
},
{
@@ -202,18 +195,25 @@ describe('@payloadcms/plugin-form-builder', () => {
version: 1,
},
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Email: {{email}}',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
[

View File

@@ -5,10 +5,9 @@ import { customersSlug } from '../shared.js'
export const Customers: CollectionConfig = {
slug: customersSlug,
timestamps: true,
admin: {
useAsTitle: 'email',
defaultColumns: ['email', 'name'],
useAsTitle: 'email',
},
auth: {
useAPIKey: true,
@@ -16,12 +15,11 @@ export const Customers: CollectionConfig = {
fields: [
{
name: 'name',
label: 'Name',
type: 'text',
label: 'Name',
},
{
name: 'subscriptions',
label: 'Subscriptions',
type: 'array',
admin: {
description:
@@ -30,7 +28,6 @@ export const Customers: CollectionConfig = {
fields: [
{
name: 'link',
label: 'Link',
type: 'ui',
admin: {
components: {
@@ -38,43 +35,44 @@ export const Customers: CollectionConfig = {
LinkToDoc({
...args,
isTestKey: process.env.PAYLOAD_PUBLIC_IS_STRIPE_TEST_KEY === 'true',
stripeResourceType: 'subscriptions',
nameOfIDField: `${args.path}.stripeSubscriptionID`,
stripeResourceType: 'subscriptions',
}),
},
},
label: 'Link',
},
{
name: 'stripeSubscriptionID',
label: 'Stripe ID',
type: 'text',
admin: {
readOnly: true,
},
label: 'Stripe ID',
},
{
name: 'stripeProductID',
label: 'Product ID',
type: 'text',
admin: {
readOnly: true,
},
label: 'Product ID',
},
{
name: 'product',
type: 'relationship',
relationTo: 'products',
admin: {
readOnly: true,
},
relationTo: 'products',
},
{
name: 'status',
label: 'Status',
type: 'select',
admin: {
readOnly: true,
},
label: 'Status',
options: [
{
label: 'Active',
@@ -107,6 +105,8 @@ export const Customers: CollectionConfig = {
],
},
],
label: 'Subscriptions',
},
],
timestamps: true,
}

View File

@@ -4,36 +4,36 @@ import { productsSlug } from '../shared.js'
export const Products: CollectionConfig = {
slug: productsSlug,
timestamps: true,
admin: {
defaultColumns: ['name'],
},
fields: [
{
name: 'name',
label: 'Name',
type: 'text',
label: 'Name',
},
{
name: 'price',
label: 'Price',
type: 'group',
admin: {
readOnly: true,
description: 'All pricing information is managed in Stripe and will be reflected here.',
readOnly: true,
},
fields: [
{
name: 'stripePriceID',
label: 'Stripe Price ID',
type: 'text',
label: 'Stripe Price ID',
},
{
name: 'stripeJSON',
label: 'Stripe JSON',
type: 'textarea',
label: 'Stripe JSON',
},
],
label: 'Price',
},
],
timestamps: true,
}

View File

@@ -2,13 +2,13 @@ import type { CollectionConfig } from '../../../packages/payload/src/collections
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
read: () => true,
},
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
{
name: 'name',

View File

@@ -32,14 +32,14 @@ export default buildConfigWithDefaults({
},
plugins: [
stripePlugin({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
isTestKey: true,
logs: true,
rest: false,
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
sync: [
{
collection: 'customers',
stripeResourceType: 'customers',
stripeResourceTypeSingular: 'customer',
fields: [
{
fieldPath: 'name',
@@ -57,11 +57,11 @@ export default buildConfigWithDefaults({
// property: 'plan.name',
// }
],
stripeResourceType: 'customers',
stripeResourceTypeSingular: 'customer',
},
{
collection: 'products',
stripeResourceType: 'products',
stripeResourceTypeSingular: 'product',
fields: [
{
fieldPath: 'name',
@@ -72,17 +72,17 @@ export default buildConfigWithDefaults({
stripeProperty: 'default_price',
},
],
stripeResourceType: 'products',
stripeResourceTypeSingular: 'product',
},
],
rest: false,
webhooks: {
'customer.subscription.created': subscriptionCreatedOrUpdated,
'customer.subscription.updated': subscriptionCreatedOrUpdated,
'customer.subscription.deleted': subscriptionDeleted,
'customer.subscription.updated': subscriptionCreatedOrUpdated,
'product.created': syncPriceJSON,
'product.updated': syncPriceJSON,
},
stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
}),
],
})

View File

@@ -1,4 +0,0 @@
module.exports = {
raw: () => {},
url: () => {},
}

View File

@@ -1 +0,0 @@
module.exports = {}

View File

@@ -72,28 +72,28 @@ export const subscriptionCreatedOrUpdated = async (args) => {
payload.logger.info(`- Subscription already exists, now updating.`)
// update existing subscription
subscriptions[indexOfSubscription] = {
stripeProductID: plan.product,
product: payloadProductID,
status: subscriptionStatus,
stripeProductID: plan.product,
}
} else {
payload.logger.info(`- This is a new subscription, now adding.`)
// create new subscription
subscriptions.push({
stripeSubscriptionID: eventID,
stripeProductID: plan.product,
product: payloadProductID,
status: subscriptionStatus,
stripeProductID: plan.product,
stripeSubscriptionID: eventID,
})
}
try {
await payload.update({
collection: 'customers',
id: foundCustomer.id,
collection: 'customers',
data: {
subscriptions,
skipSync: true,
subscriptions,
},
})

View File

@@ -49,11 +49,11 @@ export const subscriptionDeleted = async (args) => {
try {
await payload.update({
collection: 'customers',
id: foundCustomer.id,
collection: 'customers',
data: {
subscriptions,
skipSync: true,
subscriptions,
},
})

View File

@@ -41,8 +41,8 @@ export const syncPriceJSON = async (args) => {
const stripePrice = await stripe.prices.retrieve(default_price)
await payload.update({
collection: 'products',
id: payloadProductID,
collection: 'products',
data: {
price: {
stripeJSON: JSON.stringify(stripePrice),