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 autosave = searchParams.get('autosave') === 'true'
const draft = searchParams.get('draft') === 'true' const draft = searchParams.get('draft') === 'true'
try { const doc = await updateByIDOperation({
const doc = await updateByIDOperation({ id,
id, autosave,
autosave, collection,
collection, data: req.data,
data: req.data, depth: isNumber(depth) ? Number(depth) : undefined,
depth: isNumber(depth) ? Number(depth) : undefined, draft,
draft, req,
req, })
})
let message = req.t('general:updatedSuccessfully') let message = req.t('general:updatedSuccessfully')
if (draft) message = req.t('version:draftSavedSuccessfully') if (draft) message = req.t('version:draftSavedSuccessfully')
if (autosave) message = req.t('version:autosavedSuccessfully') if (autosave) message = req.t('version:autosavedSuccessfully')
return Response.json( return Response.json(
{ {
message, message,
doc, doc,
}, },
{ {
status: httpStatus.OK, status: httpStatus.OK,
}, },
) )
} catch (error) {
return Response.json(
{
message: error.message,
},
{
status: error.status || httpStatus.INTERNAL_SERVER_ERROR,
},
)
}
} }

View File

@@ -1,5 +1,5 @@
export function isNumber(value: unknown): value is number { 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 return false
} }

View File

@@ -17,6 +17,7 @@ import { devUser } from '../credentials'
type ValidPath = `/${string}` type ValidPath = `/${string}`
type RequestQuery = { type RequestQuery = {
query?: { query?: {
depth?: number
fallbackLocale?: string fallbackLocale?: string
limit?: number limit?: number
locale?: string locale?: string
@@ -27,15 +28,10 @@ type RequestQuery = {
} }
function generateQueryString(query: RequestQuery['query'], params: ParsedQs): string { function generateQueryString(query: RequestQuery['query'], params: ParsedQs): string {
const { where, limit, page, sort, ...rest } = params || {}
const whereFilter = query?.where || where
return QueryString.stringify( return QueryString.stringify(
{ {
...(rest || {}), ...(params || {}),
...(whereFilter ? { where: whereFilter } : {}), ...(query || {}),
limit: query?.limit || limit || undefined,
page: query?.page || page || undefined,
sort: query?.sort || sort || undefined,
}, },
{ {
addQueryPrefix: true, 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 () => { beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } }) const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
}) })
describe('tests', () => { 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 () => { beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } }) const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
}) })
describe('tests', () => { describe('tests', () => {

View File

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

View File

@@ -1,9 +1,15 @@
import payload from '../../packages/payload/src' import type { Payload } from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
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 () => { beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } }) const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
}) })
describe('seed', () => { describe('seed', () => {

View File

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

View File

@@ -1,10 +1,16 @@
import payload from '../../packages/payload/src' import type { Payload } from '../../packages/payload/src'
import wait from '../../packages/payload/src/utilities/wait'
import { initPayloadTest } from '../helpers/configHelpers'
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 () => { beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } }) const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
}) })
it('should add a search collection', async () => { 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' import type { PayloadRequest } from '../../../packages/payload/types'
export const seed = async (payload: Payload): Promise<boolean> => { 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 () => { beforeAll(async () => {
await initPayloadTest({ __dirname, init: { local: true } }) const config = await startMemoryDB(configPromise)
payload = await getPayload({ config })
}) })
describe('tests', () => { describe('tests', () => {

View File

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

View File

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

View File

@@ -1,18 +1,15 @@
import payload from '../../packages/payload/src' import type { Payload } from '../../packages/payload/src'
import { initPayloadTest } from '../helpers/configHelpers'
import { RESTClient } from '../helpers/rest' import { getPayload } from '../../packages/payload/src'
import { startMemoryDB } from '../startMemoryDB'
import configPromise, { pagesSlug } from './config' import configPromise, { pagesSlug } from './config'
require('isomorphic-fetch') let payload: Payload
let client
describe('Collections - Plugins', () => { describe('Collections - Plugins', () => {
beforeAll(async () => { beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } }) const config = await startMemoryDB(configPromise)
const config = await configPromise payload = await getPayload({ config })
client = new RESTClient(config, { serverURL, defaultSlug: pagesSlug })
await client.login()
}) })
it('created pages collection', async () => { 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 { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials' import { devUser } from '../credentials'
import {
chainedRelSlug,
customIdNumberSlug,
customIdSlug,
defaultAccessRelSlug,
polymorphicRelationshipsSlug,
relationSlug,
slug,
} from './shared'
const openAccess = { const openAccess = {
create: () => true, 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({ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
@@ -202,6 +203,7 @@ export default buildConfigWithDefaults({
], ],
}, },
{ {
slug: 'movieReviews',
fields: [ fields: [
{ {
name: 'movieReviewer', name: 'movieReviewer',
@@ -231,8 +233,6 @@ export default buildConfigWithDefaults({
type: 'radio', type: 'radio',
}, },
], ],
slug: 'movieReviews',
}, },
{ {
slug: polymorphicRelationshipsSlug, slug: polymorphicRelationshipsSlug,

View File

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