Files
payloadcms/test/plugin-import-export/int.spec.ts
Sasha 564fdb0e17 fix: virtual relationship fields with select (#12266)
Continuation of https://github.com/payloadcms/payload/pull/12265.

Currently, using `select` on new relationship virtual fields:
```
const doc = await payload.findByID({
  collection: 'virtual-relations',
  depth: 0,
  id,
  select: { postTitle: true },
})
```
doesn't work, because in order to calculate `post.title`, the `post`
field must be selected as well. This PR adds logic that sanitizes the
incoming `select` to include those relationships into `select` (that are
related to selected virtual fields)

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-04-30 12:27:04 -04:00

533 lines
15 KiB
TypeScript

import type { CollectionSlug, Payload } from 'payload'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { readCSV, readJSON } from './helpers.js'
import { richTextData } from './seed/richTextData.js'
let payload: Payload
let restClient: NextRESTClient
let user: any
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
describe('@payloadcms/plugin-import-export', () => {
beforeAll(async () => {
;({ payload, restClient } = await initPayloadInt(dirname))
user = await payload.login({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
})
afterAll(async () => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy()
}
})
describe('graphql', () => {
it('should not break graphql', async () => {
const query = `query {
__schema {
queryType {
name
}
}
}`
const response = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
})
.then((res) => res.json())
expect(response.error).toBeUndefined()
})
})
describe('exports', () => {
it('should create a file for collection csv from defined fields', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
sort: 'createdAt',
fields: ['id', 'title', 'group.value', 'group.array.field1', 'createdAt', 'updatedAt'],
format: 'csv',
where: {
title: { contains: 'Title ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toContain('pages.csv')
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].title).toStrictEqual('Title 0')
expect(data[0].group_value).toStrictEqual('group value')
expect(data[0].group_ignore).toBeUndefined()
expect(data[0].group_array_0_field1).toStrictEqual('test')
expect(data[0].createdAt).toBeDefined()
expect(data[0].updatedAt).toBeDefined()
})
it('should create a file for collection csv with draft data', async () => {
const draftPage = await payload.create({
collection: 'pages',
user,
data: {
title: 'Draft Page',
_status: 'published',
},
})
await payload.update({
collection: 'pages',
id: draftPage.id,
data: {
title: 'Draft Page Updated',
_status: 'draft',
},
})
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'title', '_status'],
locale: 'en',
format: 'csv',
where: {
title: { contains: 'Draft ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].title).toStrictEqual('Draft Page Updated')
expect(data[0]._status).toStrictEqual('draft')
})
it('should create a file for collection csv from one locale', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'localized'],
locale: 'en',
format: 'csv',
where: {
title: { contains: 'Localized ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].localized).toStrictEqual('en test')
})
it('should create a file for collection csv from multiple locales', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'localized'],
locale: 'all',
format: 'csv',
where: {
title: { contains: 'Localized ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].localized_en).toStrictEqual('en test')
expect(data[0].localized_es).toStrictEqual('es test')
})
it('should create a file for collection csv from array', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'array'],
format: 'csv',
where: {
title: { contains: 'Array ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].array_0_field1).toStrictEqual('foo')
expect(data[0].array_0_field2).toStrictEqual('bar')
expect(data[0].array_1_field1).toStrictEqual('foo')
expect(data[0].array_1_field2).toStrictEqual('baz')
})
it('should create a CSV file with columns matching the order of the fields array', async () => {
const fields = ['id', 'group.value', 'group.array.field1', 'title', 'createdAt', 'updatedAt']
const doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields,
format: 'csv',
where: {
title: { contains: 'Title ' },
},
},
})
const exportDoc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(exportDoc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
const buffer = fs.readFileSync(expectedPath)
const str = buffer.toString()
// Assert that the header row matches the fields array
expect(str.indexOf('id')).toBeLessThan(str.indexOf('title'))
expect(str.indexOf('group_value')).toBeLessThan(str.indexOf('title'))
expect(str.indexOf('group_value')).toBeLessThan(str.indexOf('group_array'))
expect(str.indexOf('title')).toBeLessThan(str.indexOf('createdAt'))
expect(str.indexOf('createdAt')).toBeLessThan(str.indexOf('updatedAt'))
})
it('should create a CSV file with virtual fields', async () => {
const fields = ['id', 'virtual', 'virtualRelationship']
const doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields,
format: 'csv',
where: {
title: { contains: 'Virtual ' },
},
},
})
const exportDoc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(exportDoc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
const data = await readCSV(expectedPath)
// Assert that the csv file contains the expected virtual fields
expect(data[0].virtual).toStrictEqual('virtual value')
expect(data[0].virtualRelationship).toStrictEqual('name value')
})
it('should create a file for collection csv from array.subfield', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'array.field1'],
format: 'csv',
where: {
title: { contains: 'Array Subfield ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].array_0_field1).toStrictEqual('foo')
expect(data[0].array_0_field2).toBeUndefined()
expect(data[0].array_1_field1).toStrictEqual('foo')
expect(data[0].array_1_field2).toBeUndefined()
})
it('should create a file for collection csv from hasMany field', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'hasManyNumber'],
format: 'csv',
where: {
title: { contains: 'hasMany Number ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].hasManyNumber_0).toStrictEqual('0')
expect(data[0].hasManyNumber_1).toStrictEqual('1')
expect(data[0].hasManyNumber_2).toStrictEqual('1')
expect(data[0].hasManyNumber_3).toStrictEqual('2')
expect(data[0].hasManyNumber_4).toStrictEqual('3')
})
it('should create a file for collection csv from blocks field', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'blocks'],
format: 'csv',
where: {
title: { contains: 'Blocks ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].blocks_0_blockType).toStrictEqual('hero')
expect(data[0].blocks_1_blockType).toStrictEqual('content')
})
it('should create a JSON file for collection', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'title'],
format: 'json',
sort: 'title',
where: {
title: { contains: 'JSON ' },
},
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readJSON(expectedPath)
expect(data[0].title).toStrictEqual('JSON 0')
})
it('should create an export with every field when no fields are defined', async () => {
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
format: 'json',
sort: 'title',
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readJSON(expectedPath)
expect(data[0].id).toBeDefined()
expect(data[0].title).toBeDefined()
expect(data[0].createdAt).toBeDefined()
expect(data[0].updatedAt).toBeDefined()
})
it('should create jobs task for exports', async () => {
const doc = await payload.create({
collection: 'exports-tasks' as CollectionSlug,
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'title'],
format: 'csv',
sort: 'title',
where: {
title: { contains: 'Jobs ' },
},
},
})
const { docs } = await payload.find({
collection: 'payload-jobs' as CollectionSlug,
})
const job = docs[0]
expect(job).toBeDefined()
const { input } = job
expect(input.id).toBeDefined()
expect(input.name).toBeDefined()
expect(input.format).toStrictEqual('csv')
expect(input.locale).toStrictEqual('all')
expect(input.fields).toStrictEqual(['id', 'title'])
expect(input.collectionSlug).toStrictEqual('pages')
expect(input.exportsCollection).toStrictEqual('exports-tasks')
expect(input.user).toBeDefined()
expect(input.userCollection).toBeDefined()
await payload.jobs.run()
const exportDoc = await payload.findByID({
collection: 'exports-tasks' as CollectionSlug,
id: doc.id,
})
expect(exportDoc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].title).toStrictEqual('Jobs 0')
})
// disabled so we don't always run a massive test
it.skip('should create a file from a large set of collection documents', async () => {
const allPromises = []
let promises = []
for (let i = 0; i < 100000; i++) {
promises.push(
payload.create({
collectionSlug: 'pages',
data: {
title: `Array ${i}`,
blocks: [
{
blockType: 'hero',
title: 'test',
},
{
blockType: 'content',
richText: richTextData,
},
],
},
}),
)
if (promises.length >= 500) {
await Promise.all(promises)
promises = []
}
if (i % 1000 === 0) {
console.log('created', i)
}
}
await Promise.all(promises)
console.log('seeded')
let doc = await payload.create({
collection: 'exports',
user,
data: {
collectionSlug: 'pages',
fields: ['id', 'blocks'],
format: 'csv',
},
})
doc = await payload.findByID({
collection: 'exports',
id: doc.id,
})
expect(doc.filename).toBeDefined()
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
const data = await readCSV(expectedPath)
expect(data[0].blocks_0_blockType).toStrictEqual('hero')
expect(data[0].blocks_1_blockType).toStrictEqual('content')
})
})
})