passing relationships int tests

This commit is contained in:
Jarrod Flesch
2024-02-17 01:00:48 -05:00
parent 3d99ea5cbf
commit 1c9ba5b512
20 changed files with 252 additions and 154 deletions

View File

@@ -10,39 +10,28 @@ export const updateByID: CollectionRouteHandlerWithID = async ({ req, collection
const autosave = searchParams.get('autosave') === 'true'
const draft = searchParams.get('draft') === 'true'
try {
const doc = await updateByIDOperation({
id,
autosave,
collection,
data: req.data,
depth: isNumber(depth) ? Number(depth) : undefined,
draft,
req,
})
const doc = await updateByIDOperation({
id,
autosave,
collection,
data: req.data,
depth: isNumber(depth) ? Number(depth) : undefined,
draft,
req,
})
let message = req.t('general:updatedSuccessfully')
let message = req.t('general:updatedSuccessfully')
if (draft) message = req.t('version:draftSavedSuccessfully')
if (autosave) message = req.t('version:autosavedSuccessfully')
if (draft) message = req.t('version:draftSavedSuccessfully')
if (autosave) message = req.t('version:autosavedSuccessfully')
return Response.json(
{
message,
doc,
},
{
status: httpStatus.OK,
},
)
} catch (error) {
return Response.json(
{
message: error.message,
},
{
status: error.status || httpStatus.INTERNAL_SERVER_ERROR,
},
)
}
return Response.json(
{
message,
doc,
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -1,5 +1,5 @@
export function isNumber(value: unknown): value is number {
if (typeof value === 'string' && value.trim() === '') {
if (value === null || value === undefined || (typeof value === 'string' && value.trim() === '')) {
return false
}

View File

@@ -17,6 +17,7 @@ import { devUser } from '../credentials'
type ValidPath = `/${string}`
type RequestQuery = {
query?: {
depth?: number
fallbackLocale?: string
limit?: number
locale?: string
@@ -27,15 +28,10 @@ type RequestQuery = {
}
function generateQueryString(query: RequestQuery['query'], params: ParsedQs): string {
const { where, limit, page, sort, ...rest } = params || {}
const whereFilter = query?.where || where
return QueryString.stringify(
{
...(rest || {}),
...(whereFilter ? { where: whereFilter } : {}),
limit: query?.limit || limit || undefined,
page: query?.page || page || undefined,
sort: query?.sort || sort || undefined,
...(params || {}),
...(query || {}),
},
{
addQueryPrefix: true,

View File

@@ -1,8 +1,15 @@
import { initPayloadTest } from '../helpers/configHelpers'
import type { Payload } from '../../packages/payload/src'
describe('plugin-cloud-storage', () => {
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
let payload: Payload
describe('@payloadcms/plugin-cloud-storage', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
})
describe('tests', () => {

View File

@@ -1,8 +1,15 @@
import { initPayloadTest } from '../helpers/configHelpers'
import type { Payload } from '../../packages/payload/src'
describe('Nested Docs', () => {
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
let payload: Payload
describe('@payloadcms/plugin-cloud', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
})
describe('tests', () => {

View File

@@ -1,14 +1,18 @@
import type { Payload } from '../../packages/payload/src'
import type { Form } from './payload-types'
import payload from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
import { formSubmissionsSlug, formsSlug } from './shared'
describe('Form Builder Plugin', () => {
let form: Form
let payload: Payload
let form: Form
describe('@payloadcms/plugin-form-builder', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
const formConfig: Omit<Form, 'createdAt' | 'id' | 'updatedAt'> = {
title: 'Test Form',

View File

@@ -1,9 +1,15 @@
import payload from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import type { Payload } from '../../packages/payload/src'
describe('Nested Docs', () => {
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
let payload: Payload
describe('@payloadcms/plugin-nested-docs', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
})
describe('seed', () => {

View File

@@ -1,12 +1,18 @@
import payload from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import type { Payload } from '../../packages/payload/src'
import type { Page } from './payload-types'
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
import { pagesSlug } from './shared'
describe('Redirects Plugin', () => {
let page: Page
let payload: Payload
let page: Page
describe('@payloadcms/plugin-redirects', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
page = await payload.create({
collection: 'pages',

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import type { CollectionConfig } from 'payload/dist/collections/config/types'
import { pagesSlug } from '../shared'

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import type { CollectionConfig } from 'payload/dist/collections/config/types'
import { postsSlug } from '../shared'

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
import type { CollectionConfig } from 'payload/dist/collections/config/types'
export const Users: CollectionConfig = {
slug: 'users',

View File

@@ -1,10 +1,16 @@
import payload from '../../packages/payload/src'
import wait from '../../packages/payload/src/utilities/wait'
import { initPayloadTest } from '../helpers/configHelpers'
import type { Payload } from '../../packages/payload/src'
describe('Search Plugin', () => {
import { getPayload } from '../../packages/payload/src'
import wait from '../../packages/payload/src/utilities/wait'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
let payload: Payload
describe('@payloadcms/plugin-search', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
})
it('should add a search collection', async () => {

View File

@@ -1,4 +1,4 @@
import type { Payload } from '../../../packages/payload/src'
import type { Payload } from 'payload'
import type { PayloadRequest } from '../../../packages/payload/types'
export const seed = async (payload: Payload): Promise<boolean> => {

View File

@@ -1,8 +1,15 @@
import { initPayloadTest } from '../helpers/configHelpers'
import type { Payload } from '../../packages/payload/src'
describe('plugin-sentry', () => {
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
let payload: Payload
describe('@payloadcms/plugin-sentry', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
})
describe('tests', () => {

View File

@@ -1,12 +1,17 @@
import path from 'path'
import payload from '../../packages/payload/src'
import type { Payload } from '../../packages/payload/src'
import { getPayload } from '../../packages/payload/src'
import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
import { initPayloadTest } from '../helpers/configHelpers'
import removeFiles from '../helpers/removeFiles'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
import { mediaSlug } from './shared'
describe('SEO Plugin', () => {
let payload: Payload
describe('@payloadcms/plugin-seo', () => {
let page = null
let mediaDoc = null
@@ -14,7 +19,8 @@ describe('SEO Plugin', () => {
const uploadsDir = path.resolve(__dirname, './media')
removeFiles(path.normalize(uploadsDir))
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
// Create image
const filePath = path.resolve(__dirname, './image-1.jpg')

View File

@@ -1,9 +1,15 @@
import payload from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import type { Payload } from '../../packages/payload/src'
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
let payload: Payload
describe('Stripe Plugin', () => {
beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } })
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
})
it('should create products', async () => {

View File

@@ -1,18 +1,15 @@
import payload from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import { RESTClient } from '../helpers/rest'
import type { Payload } from '../../packages/payload/src'
import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise, { pagesSlug } from './config'
require('isomorphic-fetch')
let client
let payload: Payload
describe('Collections - Plugins', () => {
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
const config = await configPromise
client = new RESTClient(config, { serverURL, defaultSlug: pagesSlug })
await client.login()
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
})
it('created pages collection', async () => {

View File

@@ -2,6 +2,15 @@ import type { CollectionConfig } from '../../packages/payload/src/collections/co
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
import {
chainedRelSlug,
customIdNumberSlug,
customIdSlug,
defaultAccessRelSlug,
polymorphicRelationshipsSlug,
relationSlug,
slug,
} from './shared'
const openAccess = {
create: () => true,
@@ -36,14 +45,6 @@ const collectionWithName = (collectionSlug: string): CollectionConfig => {
}
}
export const slug = 'posts'
export const relationSlug = 'relation'
export const defaultAccessRelSlug = 'strict-access'
export const chainedRelSlug = 'chained'
export const customIdSlug = 'custom-id'
export const customIdNumberSlug = 'custom-id-number'
export const polymorphicRelationshipsSlug = 'polymorphic-relationships'
export default buildConfigWithDefaults({
collections: [
{
@@ -202,6 +203,7 @@ export default buildConfigWithDefaults({
],
},
{
slug: 'movieReviews',
fields: [
{
name: 'movieReviewer',
@@ -231,8 +233,6 @@ export default buildConfigWithDefaults({
type: 'radio',
},
],
slug: 'movieReviews',
},
{
slug: polymorphicRelationshipsSlug,

View File

@@ -1,5 +1,6 @@
import { randomBytes } from 'crypto'
import type { Payload } from '../../packages/payload/src'
import type { PayloadRequest } from '../../packages/payload/src/types'
import type {
ChainedRelation,
@@ -10,28 +11,34 @@ import type {
Relation,
} from './payload-types'
import payload from '../../packages/payload/src'
import { getPayload } from '../../packages/payload/src'
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
import { initPayloadTest } from '../helpers/configHelpers'
import { RESTClient } from '../helpers/rest'
import config, {
import { NextRESTClient } from '../helpers/NextRESTClient'
import { startMemoryDB } from '../startMemoryDB'
import configPromise from './config'
import {
chainedRelSlug,
customIdNumberSlug,
customIdSlug,
defaultAccessRelSlug,
polymorphicRelationshipsSlug,
relationSlug,
slug,
} from './config'
usersSlug,
} from './shared'
let client: RESTClient
let restClient: NextRESTClient
let payload: Payload
let token: string
type EasierChained = { id: string; relation: EasierChained }
describe('Relationships', () => {
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
client = new RESTClient(config, { serverURL, defaultSlug: slug })
await client.login()
const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
restClient = new NextRESTClient(payload.config)
;({ token } = await restClient.login({ slug: usersSlug }).then((res) => res.json()))
})
afterAll(async () => {
@@ -146,43 +153,51 @@ describe('Relationships', () => {
})
it('should prevent an unauthorized population of strict access', async () => {
const { doc } = await client.findByID<Post>({ id: post.id, auth: false })
const doc = await restClient.GET(`/${slug}/${post.id}`).then((res) => res.json())
expect(doc.defaultAccessRelation).toEqual(defaultAccessRelation.id)
})
it('should populate strict access when authorized', async () => {
const { doc } = await client.findByID<Post>({ id: post.id })
const doc = await restClient
.GET(`/${slug}/${post.id}`, {
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
expect(doc.defaultAccessRelation).toEqual(defaultAccessRelation)
})
it('should use filterOptions to limit relationship options', async () => {
const { doc } = await client.findByID<Post>({ id: post.id })
const doc = await restClient.GET(`/${slug}/${post.id}`).then((res) => res.json())
expect(doc.filteredRelation).toMatchObject({ id: filteredRelation.id })
await client.update<Relation>({
id: filteredRelation.id,
slug: relationSlug,
data: { disableRelation: true },
await restClient.PATCH(`/${relationSlug}/${filteredRelation.id}`, {
body: JSON.stringify({
disableRelation: true,
}),
})
const { doc: docAfterUpdatingRel } = await client.findByID<Post>({ id: post.id })
const updatedDoc = await restClient.GET(`/${slug}/${post.id}`).then((res) => res.json())
// No change to existing relation
expect(docAfterUpdatingRel.filteredRelation).toMatchObject({ id: filteredRelation.id })
expect(updatedDoc.filteredRelation).toMatchObject({ id: filteredRelation.id })
// Attempt to update post with a now filtered relation
const { status, errors } = await client.update<Post>({
id: post.id,
data: { filteredRelation: filteredRelation.id },
const response = await restClient.PATCH(`/${slug}/${post.id}`, {
body: JSON.stringify({
filteredRelation: filteredRelation.id,
}),
})
const result = await response.json()
expect(errors?.[0]).toMatchObject({
expect(result.errors?.[0]).toMatchObject({
name: 'ValidationError',
message: expect.any(String),
data: expect.anything(),
})
expect(status).toEqual(400)
expect(response.status).toEqual(400)
})
it('should count totalDocs correctly when using or in where query and relation contains hasMany relationship fields', async () => {
@@ -254,13 +269,25 @@ describe('Relationships', () => {
describe('Custom ID', () => {
it('should query a custom id relation', async () => {
const { doc } = await client.findByID<Post>({ id: post.id })
expect(doc?.customIdRelation).toMatchObject({ id: generatedCustomId })
const { customIdRelation } = await restClient
.GET(`/${slug}/${post.id}`, {
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
expect(customIdRelation).toMatchObject({ id: generatedCustomId })
})
it('should query a custom id number relation', async () => {
const { doc } = await client.findByID<Post>({ id: post.id })
expect(doc?.customIdNumberRelation).toMatchObject({ id: generatedCustomIdNumber })
const { customIdNumberRelation } = await restClient
.GET(`/${slug}/${post.id}`, {
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
expect(customIdNumberRelation).toMatchObject({ id: generatedCustomIdNumber })
})
it('should validate the format of text id relationships', async () => {
@@ -284,22 +311,30 @@ describe('Relationships', () => {
})
it('should allow update removing a relationship', async () => {
const result = await client.update<Post>({
slug,
id: post.id,
data: {
relationField: null,
const response = await restClient.PATCH(`/${slug}/${post.id}`, {
headers: {
Authorization: `JWT ${token}`,
},
body: JSON.stringify({
customIdRelation: null,
}),
})
const doc = await response.json()
expect(result.status).toEqual(200)
expect(result.doc.relationField).toBeFalsy()
expect(response.status).toEqual(200)
expect(doc.relationField).toBeFalsy()
})
})
describe('depth', () => {
it('should populate to depth', async () => {
const { doc } = await client.findByID<Post>({ id: post.id, options: { depth: 2 } })
const doc = await restClient
.GET(`/${slug}/${post.id}`, {
query: {
depth: 2,
},
})
.then((res) => res.json())
const depth0 = doc?.chainedRelation as EasierChained
expect(depth0.id).toEqual(chained.id)
expect(depth0.relation.id).toEqual(chained2.id)
@@ -308,12 +343,24 @@ describe('Relationships', () => {
})
it('should only populate ID if depth 0', async () => {
const { doc } = await client.findByID<Post>({ id: post.id, options: { depth: 0 } })
const doc = await restClient
.GET(`/${slug}/${post.id}`, {
query: {
depth: 0,
},
})
.then((res) => res.json())
expect(doc?.chainedRelation).toEqual(chained.id)
})
it('should respect maxDepth at field level', async () => {
const { doc } = await client.findByID<Post>({ id: post.id, options: { depth: 1 } })
const doc = await restClient
.GET(`/${slug}/${post.id}`, {
query: {
depth: 1,
},
})
.then((res) => res.json())
expect(doc?.maxDepthRelation).toEqual(relation.id)
expect(doc?.maxDepthRelation).not.toHaveProperty('name')
// should not affect other fields
@@ -573,7 +620,7 @@ describe('Relationships', () => {
},
})
await payload.create({
collection: 'polymorphic-relationships',
collection: polymorphicRelationshipsSlug,
data: {
polymorphic: {
value: movie.id,
@@ -582,25 +629,31 @@ describe('Relationships', () => {
},
})
const query = await client.find({
slug: 'polymorphic-relationships',
query: {
and: [
{
'polymorphic.value': {
equals: movie.id,
},
const result = await restClient
.GET(`/${polymorphicRelationshipsSlug}`, {
query: {
where: {
and: [
{
'polymorphic.value': {
equals: movie.id,
},
},
{
'polymorphic.relationTo': {
equals: 'movies',
},
},
],
},
{
'polymorphic.relationTo': {
equals: 'movies',
},
},
],
},
})
},
headers: {
Authorization: `JWT ${token}`,
},
})
.then((res) => res.json())
expect(query.result.docs).toHaveLength(1)
expect(result.docs).toHaveLength(1)
})
})
})

View File

@@ -0,0 +1,8 @@
export const usersSlug = 'users'
export const slug = 'posts'
export const relationSlug = 'relation'
export const defaultAccessRelSlug = 'strict-access'
export const chainedRelSlug = 'chained'
export const customIdSlug = 'custom-id'
export const customIdNumberSlug = 'custom-id-number'
export const polymorphicRelationshipsSlug = 'polymorphic-relationships'