feat: ability to add context to payload's request object (#2796)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
68
test/hooks/collections/ContextHooks/index.ts
Normal file
68
test/hooks/collections/ContextHooks/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import payload from '../../../../src';
|
||||
import { CollectionConfig } from '../../../../src/collections/config/types';
|
||||
import type { PayloadRequest } from '../../../../src/types';
|
||||
|
||||
export const contextHooksSlug = 'context-hooks';
|
||||
const ContextHooks: CollectionConfig = {
|
||||
slug: contextHooksSlug,
|
||||
access: {
|
||||
read: () => true,
|
||||
create: () => true,
|
||||
delete: () => true,
|
||||
update: () => true,
|
||||
},
|
||||
hooks: {
|
||||
beforeOperation: [async ({ context, args }) => {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const req: PayloadRequest = args.req;
|
||||
|
||||
if (!req.query || !Object.keys(req.query).length) {
|
||||
return args;
|
||||
}
|
||||
|
||||
Object.keys(req.query).forEach((key) => {
|
||||
if (key.startsWith('context_')) {
|
||||
// Strip 'context_' from key, add it to context object and remove it from query params
|
||||
const newKey = key.substring('context_'.length);
|
||||
context[newKey] = req.query[key];
|
||||
delete req.query[key];
|
||||
}
|
||||
});
|
||||
|
||||
return args;
|
||||
}],
|
||||
beforeChange: [({ context, data, req }) => {
|
||||
if (!context.secretValue) {
|
||||
context.secretValue = 'secret';
|
||||
}
|
||||
if (req.context !== context) {
|
||||
throw new Error('req.context !== context');
|
||||
}
|
||||
return data;
|
||||
}],
|
||||
afterChange: [async ({ context, doc }) => {
|
||||
if (context.triggerAfterChange === false) { // Make sure we don't trigger afterChange again and again in an infinite loop
|
||||
return;
|
||||
}
|
||||
await payload.update({
|
||||
collection: contextHooksSlug,
|
||||
id: doc.id,
|
||||
data: {
|
||||
value: context.secretValue ?? '',
|
||||
},
|
||||
context: {
|
||||
triggerAfterChange: false, // Make sure we don't trigger afterChange again and again in an infinite loop. This should be done via context and not a potential disableHooks property, as we want to specifically test the context functionality here
|
||||
},
|
||||
});
|
||||
}],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default ContextHooks;
|
||||
@@ -5,9 +5,11 @@ import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
|
||||
import ChainingHooks from './collections/ChainingHooks';
|
||||
import Relations from './collections/Relations';
|
||||
import Users, { seedHooksUsers } from './collections/Users';
|
||||
import ContextHooks from './collections/ContextHooks';
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
ContextHooks,
|
||||
TransformHooks,
|
||||
Hooks,
|
||||
NestedAfterReadHooks,
|
||||
|
||||
@@ -12,13 +12,16 @@ import type { NestedAfterReadHook } from './payload-types';
|
||||
import { hooksUsersSlug } from './collections/Users';
|
||||
import { devUser, regularUser } from '../credentials';
|
||||
import { AuthenticationError } from '../../src/errors';
|
||||
import { contextHooksSlug } from './collections/ContextHooks';
|
||||
|
||||
let client: RESTClient;
|
||||
let apiUrl;
|
||||
|
||||
describe('Hooks', () => {
|
||||
beforeAll(async () => {
|
||||
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
|
||||
client = new RESTClient(config, { serverURL, defaultSlug: transformSlug });
|
||||
apiUrl = `${serverURL}/api`;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -151,6 +154,63 @@ describe('Hooks', () => {
|
||||
|
||||
expect(retrievedDocs[0].text).toEqual('ok!!');
|
||||
});
|
||||
|
||||
it('should pass context from beforeChange to afterChange', async () => {
|
||||
const document = await payload.create({
|
||||
collection: contextHooksSlug,
|
||||
data: {
|
||||
value: 'wrongvalue',
|
||||
},
|
||||
});
|
||||
|
||||
const retrievedDoc = await payload.findByID({
|
||||
collection: contextHooksSlug,
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
expect(retrievedDoc.value).toEqual('secret');
|
||||
});
|
||||
|
||||
it('should pass context from local API to hooks', async () => {
|
||||
const document = await payload.create({
|
||||
collection: contextHooksSlug,
|
||||
data: {
|
||||
value: 'wrongvalue',
|
||||
},
|
||||
context: {
|
||||
secretValue: 'data from local API',
|
||||
},
|
||||
});
|
||||
|
||||
const retrievedDoc = await payload.findByID({
|
||||
collection: contextHooksSlug,
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
expect(retrievedDoc.value).toEqual('data from local API');
|
||||
});
|
||||
|
||||
it('should pass context from rest API to hooks', async () => {
|
||||
const params = new URLSearchParams({
|
||||
context_secretValue: 'data from rest API',
|
||||
});
|
||||
// send context as query params. It will be parsed by the beforeOperation hook
|
||||
const response = await fetch(`${apiUrl}/${contextHooksSlug}?${params.toString()}`, {
|
||||
body: JSON.stringify({
|
||||
value: 'wrongvalue',
|
||||
}),
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
const document = (await response.json()).doc;
|
||||
|
||||
const retrievedDoc = await payload.findByID({
|
||||
collection: contextHooksSlug,
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
expect(retrievedDoc.value).toEqual('data from rest API');
|
||||
});
|
||||
});
|
||||
|
||||
describe('auth collection hooks', () => {
|
||||
|
||||
Reference in New Issue
Block a user