diff --git a/test/helpers/seed.ts b/test/helpers/seed.ts index 683e0c4fb..73db42710 100644 --- a/test/helpers/seed.ts +++ b/test/helpers/seed.ts @@ -1,10 +1,11 @@ import fs from 'fs' +import * as os from 'node:os' import path from 'path' import { type Payload } from 'payload' import { isMongoose } from './isMongoose.js' import { resetDB } from './reset.js' -import { createSnapshot, dbSnapshot, restoreFromSnapshot } from './snapshot.js' +import { createSnapshot, dbSnapshot, restoreFromSnapshot, uploadsDirCache } from './snapshot.js' type SeedFunction = (_payload: Payload) => Promise @@ -14,8 +15,13 @@ export async function seedDB({ seedFunction, snapshotKey, uploadsDir, + /** + * Always seeds, instead of restoring from snapshot for consecutive test runs + */ + alwaysSeed = false, }: { _payload: Payload + alwaysSeed?: boolean collectionSlugs: string[] seedFunction: SeedFunction /** @@ -28,6 +34,25 @@ export async function seedDB({ * Reset database */ await resetDB(_payload, collectionSlugs) + /** + * Delete uploads directory if it exists + */ + if (uploadsDir) { + try { + // Attempt to clear the uploads directory if it exists + await fs.promises.access(uploadsDir) + const files = await fs.promises.readdir(uploadsDir) + for (const file of files) { + await fs.promises.rm(path.join(uploadsDir, file)) + } + } catch (error) { + if (error.code !== 'ENOENT') { + // If the error is not because the directory doesn't exist + console.error('Error in operation (deleting uploads dir):', error) + throw error + } + } + } /** * Mongoose & Postgres: Restore snapshot of old data if available @@ -36,8 +61,40 @@ export async function seedDB({ * This does not work if I run payload.db.init or payload.db.connect anywhere. Thus, when resetting the database, we are not dropping the schema, but are instead only deleting the table values */ let restored = false - if (dbSnapshot[snapshotKey] && Object.keys(dbSnapshot[snapshotKey]).length) { + if (!alwaysSeed && dbSnapshot[snapshotKey] && Object.keys(dbSnapshot[snapshotKey]).length) { await restoreFromSnapshot(_payload, snapshotKey, collectionSlugs) + + /** + * Restore uploads dir if it exists + */ + if (uploadsDir && fs.existsSync(uploadsDirCache.path)) { + // move all files from inside uploadsDirCacheFolder to uploadsDir + await fs.promises + .readdir(uploadsDirCache.path, { withFileTypes: true }) + .then(async (files) => { + for (const file of files) { + if (file.isDirectory()) { + await fs.promises.mkdir(path.join(uploadsDir, file.name), { + recursive: true, + }) + await fs.promises.copyFile( + path.join(uploadsDirCache.path, file.name), + path.join(uploadsDir, file.name), + ) + } else { + await fs.promises.copyFile( + path.join(uploadsDirCache.path, file.name), + path.join(uploadsDir, file.name), + ) + } + } + }) + .catch((err) => { + console.error('Error in operation (restoring uploads dir):', err) + throw err + }) + } + restored = true } @@ -49,6 +106,7 @@ export async function seedDB({ if (isMongoose(_payload)) { await Promise.all([ ...collectionSlugs.map(async (collectionSlug) => { + // @ts-expect-error TODO: Type this better await _payload.db.collections[collectionSlug].createIndexes() }), ]) @@ -61,33 +119,53 @@ export async function seedDB({ return } - /** - * Delete uploads directory only if no snapshot was restored. - * The snapshot restoration only restores the database state, not the uploads directory. - * If we ran it after or before restoring the snapshot, we would have NO upload files anymore, as they are not restored from the snapshot. And after snapshot - * restoration the seed process is not run again - */ - if (uploadsDir) { - try { - // Attempt to clear the uploads directory if it exists - await fs.promises.access(uploadsDir) - const files = await fs.promises.readdir(uploadsDir) - for (const file of files) { - await fs.promises.rm(path.join(uploadsDir, file)) - } - } catch (error) { - if (error.code !== 'ENOENT') { - // If the error is not because the directory doesn't exist - console.error('Error in operation:', error) - throw error - } - } - } - /** * Seed the database with data and save it to a snapshot **/ await seedFunction(_payload) - await createSnapshot(_payload, snapshotKey, collectionSlugs) + if (!alwaysSeed) { + await createSnapshot(_payload, snapshotKey, collectionSlugs) + } + + /** + * Cache uploads dir to a cache folder if uploadsDir exists + */ + if (!alwaysSeed && uploadsDir && fs.existsSync(uploadsDir)) { + if (!uploadsDirCache.path) { + // Define new cache folder path to the OS temp directory (well a random folder inside it) + uploadsDirCache.path = path.join(os.tmpdir(), `payload-e2e-tests-uploads-cache`) + } + + // delete the cache folder if it exists + if (fs.existsSync(uploadsDirCache.path)) { + await fs.promises.rm(uploadsDirCache.path, { recursive: true }) + } + await fs.promises.mkdir(uploadsDirCache.path, { recursive: true }) + // recursively move all files and directories from uploadsDir to uploadsDirCacheFolder + await fs.promises + .readdir(uploadsDir, { withFileTypes: true }) + .then(async (files) => { + for (const file of files) { + if (file.isDirectory()) { + await fs.promises.mkdir(path.join(uploadsDirCache.path, file.name), { + recursive: true, + }) + await fs.promises.copyFile( + path.join(uploadsDir, file.name), + path.join(uploadsDirCache.path, file.name), + ) + } else { + await fs.promises.copyFile( + path.join(uploadsDir, file.name), + path.join(uploadsDirCache.path, file.name), + ) + } + } + }) + .catch((err) => { + console.error('Error in operation (creating snapshot of uploads dir):', err) + throw err + }) + } } diff --git a/test/helpers/snapshot.ts b/test/helpers/snapshot.ts index 1b8f83d17..75844fdb8 100644 --- a/test/helpers/snapshot.ts +++ b/test/helpers/snapshot.ts @@ -6,6 +6,11 @@ import { sql } from 'drizzle-orm' import { isMongoose } from './isMongoose.js' +export const uploadsDirCache: { + path: null | string +} = { + path: null, +} export const dbSnapshot = {} async function createMongooseSnapshot(collectionsObj, snapshotKey: string) {