Files
payloadcms/test/uploads/int.spec.ts
Alessio Gravili b4578c10a4 feat: security improvements and features (#3214)
BREAKING CHANGE:
- Unhandled Errors are now omitted by default. This can be breaking if people depend on those error messages. Now, it will just say "Something went wrong.".

* chore: slightly improved testing of registration via graphql

Signed-off-by: Vsevolod Volkov <st.lyn4@gmail.com>

* chore: hiding details of internal errors from responses

Signed-off-by: Vsevolod Volkov <st.lyn4@gmail.com>

* feat: ability to remove authorization tokens from response bodies

Signed-off-by: Vsevolod Volkov <st.lyn4@gmail.com>

* chore: add section for design contributions in contributing.md

* feat: add afterOperation hook (#2697)

* feat: add afterOperation hook for Find operation

* docs: change #afterOperation to #afteroperation

* chore: extract afterOperation in function

* chore: implement afterChange in operations

* docs: use proper CollectionAfterOperationHook

* chore: remove outdated info

* chore: types afterOperation hook

* chore: improves afterOperation tests

* docs: updates description of afterOperation hook

* chore: improve typings

* chore: improve types

* chore: rename index.tsx => index.ts

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Co-authored-by: Alessio Gravili <alessio@gravili.de>

* chore: remove swc version pin (#3179)

* fix: WhereBuilder component does not accept all valid Where queries (#3087)

* chore: add jsDocs for ListControls

* chore: add jsDocs for ListView

* chore: add jsDocs for WhereBuilder

* chore: add comment

* chore: remove unnecessary console log

* chore: improve operator type

* fix: transform where queries which aren't necessarily incorrect, and improve their validation

* chore: add type to import

* fix: do not merge existing old query params with new ones if the existing old ones got transformed and are not valid, as that would cause duplicates

* chore: sort imports and remove extra validation

* fix: transformWhereQuery logic

* chore: add back extra validation

* chore: add e2e tests

* chore(test): adds test to ensure relationship returns over 10 docs (#3181)

* chore(test): adds test to ensure relationship returns over 10 docs

* chore: remove unnecessary movieDocs variable

* fix: passes in height to resizeOptions upload option to allow height resize (#3171)

* docs: fixes syntax error in rich-text.mdx that was breaking build

* docs: removes auto-formatting from rich-text.mdx (#3188)

* feat: Improve admin dashboard accessibility (#3053)

Co-authored-by: Alessio Gravili <alessio@gravili.de>

* feat: improve field ops (#3172)

Co-authored-by: PatrikKozak <patrik@trbl.design>

* chore: file cleanup (#3190)

* chore(release): v1.14.0

* chore: improve ts typing in sanitization functions (#3194)

* chore(templates): default port on website

* chore(templates): safely handles bad network requests

* chore(templates): implements draft preview and on-demand revalidation

* chore(templates): renders static cart page fallback

* chore(examples): updates draft-preview next-app example to use revalidateTag (#3199)

* feat: query support for geo within and intersects + dynamic GraphQL operator types (#3183)

Co-authored-by: Lucas Blancas <lablancas@gmail.com>

* chore: improve checkboxes (#3191)

* chore: improve filtering for hasMany number field (#3193)

* chore: improve fiiltering for hasMany number field

* chore: add translation for 'items' and replace rows with items

* chore: new exceededLimit key

* Revert "chore: add translation for 'items' and replace rows with items"

This reverts commit 3a91dabdfd1ddf9024f73a00a408c0312f0dfd06.

* chore: undo adding items key in translation schema

* chore: new limitReached key

* chore: remove unnecessary exceededLimit key

* chore: spelling improvements

* chore: update test build config import

---------

Signed-off-by: Vsevolod Volkov <st.lyn4@gmail.com>
Co-authored-by: Vsevolod Volkov <st.lyn4@gmail.com>
Co-authored-by: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com>
2023-08-22 23:30:22 +02:00

551 lines
17 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import FormData from 'form-data';
import payload from '../../src';
import getFileByPath from '../../src/uploads/getFileByPath';
import { initPayloadTest } from '../helpers/configHelpers';
import { RESTClient } from '../helpers/rest';
import configPromise, { enlargeSlug, mediaSlug, reduceSlug, relationSlug } from './config';
const stat = promisify(fs.stat);
require('isomorphic-fetch');
describe('Collections - Uploads', () => {
let client: RESTClient;
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
const config = await configPromise;
client = new RESTClient(config, { serverURL, defaultSlug: mediaSlug });
await client.login();
});
describe('REST', () => {
describe('create', () => {
it('creates from form data given a png', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './image.png')));
const { status, doc } = await client.create({
file: true,
data: formData,
});
expect(status).toBe(201);
const { sizes } = doc;
const expectedPath = path.join(__dirname, './media');
// Check for files
expect(await fileExists(path.join(expectedPath, doc.filename))).toBe(true);
expect(
await fileExists(path.join(expectedPath, sizes.maintainedAspectRatio.filename)),
).toBe(true);
expect(await fileExists(path.join(expectedPath, sizes.tablet.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, sizes.mobile.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, sizes.icon.filename))).toBe(true);
// Check api response
expect(doc.mimeType).toEqual('image/png');
expect(sizes.maintainedAspectRatio.url).toContain('/media/image');
expect(sizes.maintainedAspectRatio.url).toContain('.png');
expect(sizes.maintainedAspectRatio.width).toEqual(1024);
expect(sizes.maintainedAspectRatio.height).toEqual(1024);
expect(sizes).toHaveProperty('tablet');
expect(sizes).toHaveProperty('mobile');
expect(sizes).toHaveProperty('icon');
});
it('creates from form data given an svg', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './image.svg')));
const { status, doc } = await client.create({
file: true,
data: formData,
});
expect(status).toBe(201);
// Check for files
expect(await fileExists(path.join(__dirname, './media', doc.filename))).toBe(true);
// Check api response
expect(doc.mimeType).toEqual('image/svg+xml');
expect(doc.sizes.maintainedAspectRatio.url).toBeUndefined();
expect(doc.width).toBeDefined();
expect(doc.height).toBeDefined();
});
});
it('creates images that do not require all sizes', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './small.png')));
const { status, doc } = await client.create({
file: true,
data: formData,
});
expect(status).toBe(201);
const expectedPath = path.join(__dirname, './media');
// Check for files
expect(await fileExists(path.join(expectedPath, doc.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, 'small-640x480.png'))).toBe(false);
expect(await fileExists(path.join(expectedPath, doc.sizes.icon.filename))).toBe(true);
// Check api response
expect(doc.sizes.tablet.filename).toBeNull();
expect(doc.sizes.icon.filename).toBeDefined();
});
it('creates images from a different format', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './image.jpg')));
const { status, doc } = await client.create({
file: true,
data: formData,
});
expect(status).toBe(201);
const expectedPath = path.join(__dirname, './media');
// Check for files
expect(await fileExists(path.join(expectedPath, doc.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, doc.sizes.tablet.filename))).toBe(true);
// Check api response
expect(doc.filename).toContain('.png');
expect(doc.mimeType).toEqual('image/png');
expect(doc.sizes.maintainedAspectRatio.filename).toContain('.png');
expect(doc.sizes.maintainedAspectRatio.mimeType).toContain('image/png');
expect(doc.sizes.differentFormatFromMainImage.filename).toContain('.jpg');
expect(doc.sizes.differentFormatFromMainImage.mimeType).toContain('image/jpeg');
});
it('creates media without storing a file', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './small.png')));
// unstored media
const { status, doc } = await client.create({
slug: 'unstored-media',
file: true,
data: formData,
});
expect(status).toBe(201);
// Check for files
expect(await !fileExists(path.join(__dirname, './media', doc.filename))).toBe(false);
// Check api response
expect(doc.filename).toBeDefined();
});
it('should enlarge images if resize options `withoutEnlargement` is set to false', async () => {
const small = await getFileByPath(path.resolve(__dirname, './small.png'));
const result = await payload.create({
collection: enlargeSlug,
data: {},
file: small,
});
expect(result).toBeTruthy();
const { sizes } = result;
const expectedPath = path.join(__dirname, './media/enlarge');
// Check for files
expect(await fileExists(path.join(expectedPath, small.name))).toBe(true);
expect(await fileExists(path.join(expectedPath, sizes.resizedLarger.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, sizes.resizedSmaller.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, sizes.accidentalSameSize.filename))).toBe(
true,
);
expect(await fileExists(path.join(expectedPath, sizes.sameSizeWithNewFormat.filename))).toBe(
true,
);
// Check api response
expect(sizes.sameSizeWithNewFormat.mimeType).toBe('image/jpeg');
expect(sizes.sameSizeWithNewFormat.filename).toBe('small-320x80.jpg');
expect(sizes.resizedLarger.mimeType).toBe('image/png');
expect(sizes.resizedLarger.filename).toBe('small-640x480.png');
expect(sizes.resizedSmaller.mimeType).toBe('image/png');
expect(sizes.resizedSmaller.filename).toBe('small-180x50.png');
expect(sizes.accidentalSameSize.mimeType).toBe('image/png');
expect(sizes.accidentalSameSize.filename).toBe('small-320x80.png');
await payload.delete({
collection: enlargeSlug,
id: result.id,
});
});
// This test makes sure that the image resizing is not prevented if only one dimension is larger (due to payload preventing enlargement by default)
it('should resize images if one desired dimension is smaller and the other is larger', async () => {
const small = await getFileByPath(path.resolve(__dirname, './small.png'));
const result = await payload.create({
collection: enlargeSlug,
data: {},
file: small,
});
expect(result).toBeTruthy();
const { sizes } = result;
const expectedPath = path.join(__dirname, './media/enlarge');
// Check for files
expect(await fileExists(path.join(expectedPath, sizes.widthLowerHeightLarger.filename))).toBe(
true,
);
// Check api response
expect(sizes.widthLowerHeightLarger.mimeType).toBe('image/png');
expect(sizes.widthLowerHeightLarger.filename).toBe('small-300x300.png');
await payload.delete({
collection: enlargeSlug,
id: result.id,
});
});
it('should not reduce images if resize options `withoutReduction` is set to true', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './small.png')));
const small = await getFileByPath(path.resolve(__dirname, './small.png'));
const result = await payload.create({
collection: reduceSlug,
data: {},
file: small,
});
expect(result).toBeTruthy();
const { sizes } = result;
const expectedPath = path.join(__dirname, './media/reduce');
// Check for files
expect(await fileExists(path.join(expectedPath, small.name))).toBe(true);
expect(await fileExists(path.join(expectedPath, 'small-640x480.png'))).toBe(false);
expect(await fileExists(path.join(expectedPath, 'small-180x50.png'))).toBe(false);
expect(await fileExists(path.join(expectedPath, sizes.accidentalSameSize.filename))).toBe(
true,
);
expect(await fileExists(path.join(expectedPath, sizes.sameSizeWithNewFormat.filename))).toBe(
true,
);
// Check api response
expect(sizes.sameSizeWithNewFormat.mimeType).toBe('image/jpeg');
expect(sizes.sameSizeWithNewFormat.filename).toBe('small-320x80.jpg');
expect(sizes.resizedLarger.mimeType).toBeNull();
expect(sizes.resizedLarger.filename).toBeNull();
expect(sizes.accidentalSameSize.mimeType).toBe('image/png');
expect(sizes.resizedSmaller.filename).toBe('small-320x80.png');
expect(sizes.accidentalSameSize.mimeType).toBe('image/png');
expect(sizes.accidentalSameSize.filename).toBe('small-320x80.png');
});
});
it('update', async () => {
// Create image
const filePath = path.resolve(__dirname, './image.png');
const file = await getFileByPath(filePath);
file.name = 'renamed.png';
const mediaDoc = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './small.png')));
const { status } = await client.update({
id: mediaDoc.id,
data: formData,
});
expect(status).toBe(200);
const expectedPath = path.join(__dirname, './media');
// Check that previously existing files were removed
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(true);
});
it('update - update many', async () => {
// Create image
const filePath = path.resolve(__dirname, './image.png');
const file = await getFileByPath(filePath);
file.name = 'renamed.png';
const mediaDoc = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './small.png')));
const { status } = await client.updateMany({
// id: mediaDoc.id,
where: {
id: { equals: mediaDoc.id },
},
data: formData,
});
expect(status).toBe(200);
const expectedPath = path.join(__dirname, './media');
// Check that previously existing files were removed
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, mediaDoc.sizes.icon.filename))).toBe(true);
});
it('should remove existing media on re-upload', async () => {
// Create temp file
const filePath = path.resolve(__dirname, './temp.png');
const file = await getFileByPath(filePath);
file.name = 'temp.png';
const mediaDoc = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const expectedPath = path.join(__dirname, './media');
// Check that the temp file was created
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(true);
// Replace the temp file with a new one
const newFilePath = path.resolve(__dirname, './temp-renamed.png');
const newFile = await getFileByPath(newFilePath);
newFile.name = 'temp-renamed.png';
const updatedMediaDoc = await payload.update({
collection: mediaSlug,
id: mediaDoc.id,
file: newFile,
data: {},
});
// Check that the replacement file was created and the old one was removed
expect(await fileExists(path.join(expectedPath, updatedMediaDoc.filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(false);
});
it('should remove existing media on re-upload - update many', async () => {
// Create temp file
const filePath = path.resolve(__dirname, './temp.png');
const file = await getFileByPath(filePath);
file.name = 'temp.png';
const mediaDoc = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const expectedPath = path.join(__dirname, './media');
// Check that the temp file was created
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(true);
// Replace the temp file with a new one
const newFilePath = path.resolve(__dirname, './temp-renamed.png');
const newFile = await getFileByPath(newFilePath);
newFile.name = 'temp-renamed.png';
const updatedMediaDoc = await payload.update({
collection: mediaSlug,
where: {
id: { equals: mediaDoc.id },
},
file: newFile,
data: {},
});
// Check that the replacement file was created and the old one was removed
expect(await fileExists(path.join(expectedPath, updatedMediaDoc.docs[0].filename))).toBe(true);
expect(await fileExists(path.join(expectedPath, mediaDoc.filename))).toBe(false);
});
it('should remove extra sizes on update', async () => {
const filePath = path.resolve(__dirname, './image.png');
const file = await getFileByPath(filePath);
const small = await getFileByPath(path.resolve(__dirname, './small.png'));
const { id } = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const doc = await payload.update({
collection: mediaSlug,
id,
data: {},
file: small,
});
expect(doc.sizes.icon).toBeDefined();
expect(doc.sizes.tablet.width).toBeNull();
});
it('should remove extra sizes on update - update many', async () => {
const filePath = path.resolve(__dirname, './image.png');
const file = await getFileByPath(filePath);
const small = await getFileByPath(path.resolve(__dirname, './small.png'));
const { id } = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const doc = await payload.update({
collection: mediaSlug,
where: {
id: { equals: id },
},
data: {},
file: small,
});
expect(doc.docs[0].sizes.icon).toBeDefined();
expect(doc.docs[0].sizes.tablet.width).toBeNull();
});
it('should allow update removing a relationship', async () => {
const filePath = path.resolve(__dirname, './image.png');
const file = await getFileByPath(filePath);
file.name = 'renamed.png';
const { id } = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const related = await payload.create({
collection: relationSlug,
data: {
image: id,
},
});
const doc = await payload.update({
collection: relationSlug,
id: related.id,
data: {
image: null,
},
});
expect(doc.image).toBeNull();
});
it('should allow update removing a relationship - update many', async () => {
const filePath = path.resolve(__dirname, './image.png');
const file = await getFileByPath(filePath);
file.name = 'renamed.png';
const { id } = await payload.create({
collection: mediaSlug,
data: {},
file,
});
const related = await payload.create({
collection: relationSlug,
data: {
image: id,
},
});
const doc = await payload.update({
collection: relationSlug,
where: {
id: { equals: related.id },
},
data: {
image: null,
},
});
expect(doc.docs[0].image).toBeNull();
});
it('delete', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './image.png')));
const { doc } = await client.create({
file: true,
data: formData,
});
const { status } = await client.delete(doc.id, {
id: doc.id,
});
expect(status).toBe(200);
expect(await fileExists(path.join(__dirname, doc.filename))).toBe(false);
});
it('delete - update many', async () => {
const formData = new FormData();
formData.append('file', fs.createReadStream(path.join(__dirname, './image.png')));
const { doc } = await client.create({
file: true,
data: formData,
});
const { errors } = await client.deleteMany({
slug: mediaSlug,
where: {
id: { equals: doc.id },
},
});
expect(errors).toHaveLength(0);
expect(await fileExists(path.join(__dirname, doc.filename))).toBe(false);
});
});
async function fileExists(fileName: string): Promise<boolean> {
try {
await stat(fileName);
return true;
} catch (err) {
return false;
}
}