test: refactor all test directories into one

This commit is contained in:
Elliot DeNolf
2022-07-15 12:51:43 -07:00
parent 40b6afff2d
commit ba79b4446c
54 changed files with 224 additions and 698 deletions

View File

@@ -2,8 +2,9 @@ module.exports = {
verbose: true, verbose: true,
testEnvironment: 'node', testEnvironment: 'node',
testMatch: [ testMatch: [
'**/test/int/**/*spec.ts', '**/test/**/*int.spec.ts',
], ],
globalSetup: './test/jest.setup.ts',
testTimeout: 15000, testTimeout: 15000,
moduleNameMapper: { moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/webpack/mocks/fileMock.js', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/webpack/mocks/fileMock.js',

View File

@@ -4,7 +4,7 @@ import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = { const config: PlaywrightTestConfig = {
// Look for test files in the "tests" directory, relative to this configuration file // Look for test files in the "tests" directory, relative to this configuration file
testDir: 'test', testDir: 'test',
testMatch: 'e2e.spec.ts', testMatch: '*e2e.spec.ts',
workers: 999, workers: 999,
timeout: 600000, timeout: 600000,
}; };

View File

@@ -1,29 +0,0 @@
import { APIError } from '.';
describe('Errors', () => {
describe('APIError', () => {
it('should handle an error message', () => {
const error = new APIError('my message', 400, false);
expect(error.message).toStrictEqual('my message');
});
// it('should handle an array', () => {
// const errors = [
// {
// error: 'some error description',
// },
// {
// error: 'some error description 2',
// },
// ];
// const error = new APIError(errors, 400, false);
// expect(error.message).toStrictEqual(JSON.stringify(errors));
// });
// it('should handle an object', () => {
// const myFancyErrorObject = { someProp: 'someDetail ' };
// const error = new APIError(myFancyErrorObject, 400, false);
// expect(error.message).toStrictEqual(JSON.stringify(myFancyErrorObject));
// });
});
});

View File

@@ -1,55 +0,0 @@
import merge from 'deepmerge';
import path from 'path';
import { v4 as uuid } from 'uuid';
import { CollectionConfig } from '../../collections/config/types';
import { Config, SanitizedConfig, InitOptions } from '../../config/types';
import { buildConfig } from '../../config/build';
import payload from '../..';
const Admins: CollectionConfig = {
slug: 'admins',
auth: true,
fields: [
// Email added by default
{
name: 'name',
type: 'text',
},
],
};
const baseConfig: Config = {
serverURL: 'http://localhost:3000',
admin: {
user: Admins.slug,
},
collections: [
Admins,
],
};
export function generateTestConfig(overrides?: Partial<Config>): SanitizedConfig {
return buildConfig(merge(baseConfig, overrides));
}
type InitPayloadTestOptions = { initOptions?: Partial<InitOptions> }
export function initPayloadTest(dirName: string, initOptions?: InitPayloadTestOptions): void {
process.env.PAYLOAD_CONFIG_PATH = path.resolve(dirName, './payload.config.ts');
payload.init({
local: true,
mongoURL: `mongodb://localhost/${uuid()}`,
secret: uuid(),
// TODO: Figure out how to handle express
...initOptions,
});
}
export const openAccess: CollectionConfig['access'] = {
read: () => true,
create: () => true,
delete: () => true,
update: () => true,
};

View File

@@ -1,10 +1,10 @@
import { devUser } from '../../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
import type { ReadOnlyCollection } from './payload-types';
export const slug = 'access-controls'; export const slug = 'access-controls';
export const readOnlySlug = 'read-only-collection'; export const readOnlySlug = 'read-only-collection';
export const restrictedSlug = 'restricted'; export const restrictedSlug = 'restricted';
export const restrictedVersionsSlug = 'restricted-versions';
export default buildConfig({ export default buildConfig({
collections: [ collections: [
@@ -45,6 +45,19 @@ export default buildConfig({
delete: () => false, delete: () => false,
}, },
}, },
{
slug: restrictedVersionsSlug,
versions: true,
fields: [
{
name: 'name',
type: 'text',
},
],
access: {
readVersions: () => false,
},
},
], ],
onInit: async (payload) => { onInit: async (payload) => {
await payload.create({ await payload.create({
@@ -62,11 +75,18 @@ export default buildConfig({
}, },
}); });
await payload.create<ReadOnlyCollection>({ await payload.create({
collection: readOnlySlug, collection: readOnlySlug,
data: { data: {
name: 'read-only', name: 'read-only',
}, },
}); });
await payload.create({
collection: restrictedVersionsSlug,
data: {
name: 'versioned',
},
});
}, },
}); });

View File

@@ -1,16 +1,15 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import payload from '../../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login } from '../helpers'; import { login } from '../helpers';
import { readOnlySlug, restrictedSlug, slug } from './config'; import { restrictedVersionsSlug, readOnlySlug, restrictedSlug, slug } from './config';
import type { ReadOnlyCollection } from './payload-types'; import type { ReadOnlyCollection, RestrictedVersion } from './payload-types';
/** /**
* TODO: Access Control * TODO: Access Control
* prevent user from logging in (canAccessAdmin) * prevent user from logging in (canAccessAdmin)
* no version controls is no access
* *
* FSK: 'should properly prevent / allow public users from reading a restricted field' * FSK: 'should properly prevent / allow public users from reading a restricted field'
* *
@@ -24,6 +23,7 @@ describe('access control', () => {
let url: AdminUrlUtil; let url: AdminUrlUtil;
let restrictedUrl: AdminUrlUtil; let restrictedUrl: AdminUrlUtil;
let readoOnlyUrl: AdminUrlUtil; let readoOnlyUrl: AdminUrlUtil;
let restrictedVersionsUrl: AdminUrlUtil;
beforeAll(async ({ browser }) => { beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadE2E(__dirname); const { serverURL } = await initPayloadE2E(__dirname);
@@ -31,6 +31,7 @@ describe('access control', () => {
url = new AdminUrlUtil(serverURL, slug); url = new AdminUrlUtil(serverURL, slug);
restrictedUrl = new AdminUrlUtil(serverURL, restrictedSlug); restrictedUrl = new AdminUrlUtil(serverURL, restrictedSlug);
readoOnlyUrl = new AdminUrlUtil(serverURL, readOnlySlug); readoOnlyUrl = new AdminUrlUtil(serverURL, readOnlySlug);
restrictedVersionsUrl = new AdminUrlUtil(serverURL, restrictedVersionsSlug);
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
@@ -121,7 +122,7 @@ describe('access control', () => {
await expect(page.locator(`#card-${readOnlySlug}`)).not.toHaveClass('card__actions'); await expect(page.locator(`#card-${readOnlySlug}`)).not.toHaveClass('card__actions');
}); });
test('edit view should not have buttons', async () => { test('edit view should not have actions buttons', async () => {
await page.goto(readoOnlyUrl.edit(existingDoc.id)); await page.goto(readoOnlyUrl.edit(existingDoc.id));
await expect(page.locator('.collection-edit__collection-actions li')).toHaveCount(0); await expect(page.locator('.collection-edit__collection-actions li')).toHaveCount(0);
}); });
@@ -131,6 +132,24 @@ describe('access control', () => {
await expect(page.locator('#field-name')).toBeDisabled(); await expect(page.locator('#field-name')).toBeDisabled();
}); });
}); });
describe('readVersions', () => {
let existingDoc: RestrictedVersion;
beforeAll(async () => {
existingDoc = await payload.create<RestrictedVersion>({
collection: restrictedVersionsSlug,
data: {
name: 'name',
},
});
});
test('versions sidebar should not show', async () => {
await page.goto(restrictedVersionsUrl.edit(existingDoc.id));
await expect(page.locator('.versions-count')).not.toBeVisible();
});
});
}); });
async function createDoc(data: any): Promise<{ id: string }> { async function createDoc(data: any): Promise<{ id: string }> {

View File

@@ -35,6 +35,16 @@ export interface ReadOnlyCollection {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "restricted-versions".
*/
export interface RestrictedVersion {
id: string;
name?: string;
createdAt: string;
updatedAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users". * via the `definition` "users".

View File

@@ -1,6 +1,6 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import payload from '../../../src'; import payload from '../../src';
import config from './config'; import config from './config';
import type { Array as ArrayCollection } from './payload-types'; import type { Array as ArrayCollection } from './payload-types';

69
test/auth/e2e.spec.ts Normal file
View File

@@ -0,0 +1,69 @@
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadTest } from '../helpers/configHelpers';
import { firstRegister } from '../helpers';
import { slug } from './config';
/**
* TODO: Auth
* change password
* unlock
* generate api key
* log out
*/
const { beforeAll, describe } = test;
let url: AdminUrlUtil;
// describe('authentication', () => {
// let page: Page;
// beforeAll(async ({ browser }) => {
// const { serverURL } = await initPayloadTest({
// __dirname,
// init: {
// local: false,
// },
// });
// // await clearDocs(); // Clear any seeded data from onInit
// url = new AdminUrlUtil(serverURL, slug);
// const context = await browser.newContext();
// page = await context.newPage();
// await firstRegister({ page, serverURL });
// });
// describe('Authentication', () => {
// test(should login and logout', () => {
// expect(1).toEqual(1);
// });
// test(should logout', () => {
// expect(1).toEqual(1);
// });
// test(should allow change password', () => {
// expect(1).toEqual(1);
// });
// test(should reset password', () => {
// expect(1).toEqual(1);
// });
// test(should lockout after reaching max login attempts', () => {
// expect(1).toEqual(1);
// });
// test(should prevent login for locked user', () => {
// expect(1).toEqual(1);
// });
// test(should unlock user', () => {
// expect(1).toEqual(1);
// });
// test(should not login without verify', () => {
// expect(1).toEqual(1);
// });
// test(should allow generate api keys', () => {
// expect(1).toEqual(1);
// });
// });
// });

37
test/buildConfig.ts Normal file
View File

@@ -0,0 +1,37 @@
import merge from 'deepmerge';
import { Config, SanitizedConfig } from '../src/config/types';
import { buildConfig as buildPayloadConfig } from '../src/config/build';
const baseConfig: Config = {
typescript: {
outputFile: process.env.PAYLOAD_TS_OUTPUT_PATH,
},
};
export function buildConfig(overrides?: Partial<Config>): SanitizedConfig {
if (process.env.NODE_ENV === 'test') {
baseConfig.admin = {
...baseConfig.admin || {},
webpack: (config) => {
const existingConfig = typeof overrides?.admin?.webpack === 'function' ? overrides.admin.webpack(config) : config;
return {
...existingConfig,
cache: {
type: 'memory',
},
};
},
};
}
console.log(process.env.PAYLOAD_DISABLE_ADMIN);
if (process.env.PAYLOAD_DISABLE_ADMIN === 'true') {
console.log('disabling admin');
if (typeof baseConfig.admin !== 'object') baseConfig.admin = {};
baseConfig.admin.disable = true;
}
return buildPayloadConfig(merge(baseConfig, overrides || {}));
}

View File

@@ -1,8 +1,8 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import config from './config'; import config from './config';
import payload from '../../../src'; import payload from '../../src';
import { RESTClient } from '../../helpers/rest'; import { RESTClient } from '../helpers/rest';
import type { Post } from './payload-types'; import type { Post } from './payload-types';
const collection = config.collections[0]?.slug; const collection = config.collections[0]?.slug;

View File

@@ -1,5 +1,5 @@
import type { CollectionConfig } from '../../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import { devUser } from '../../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
import type { Post } from './payload-types'; import type { Post } from './payload-types';

View File

@@ -1,11 +1,11 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import type { Relation } from './config'; import type { Relation } from './config';
import config, { slug, relationSlug } from './config'; import config, { slug, relationSlug } from './config';
import payload from '../../../src'; import payload from '../../src';
import { RESTClient } from '../../helpers/rest'; import { RESTClient } from '../helpers/rest';
import { mapAsync } from '../../../src/utilities/mapAsync';
import type { Post } from './payload-types'; import type { Post } from './payload-types';
import { mapAsync } from '../../src/utilities/mapAsync';
let client: RESTClient; let client: RESTClient;

View File

@@ -1,5 +1,5 @@
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { devUser } from '../../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
export const slug = 'posts'; export const slug = 'posts';

View File

@@ -1,13 +1,13 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import payload from '../../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers'; import { login, saveDocAndAssert } from '../helpers';
import type { Post } from './config'; import type { Post } from './config';
import { slug } from './config'; import { slug } from './config';
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import wait from '../../../src/utilities/wait'; import wait from '../../src/utilities/wait';
const { afterEach, beforeAll, beforeEach, describe } = test; const { afterEach, beforeAll, beforeEach, describe } = test;

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-console */
import express from 'express'; import express from 'express';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import payload from '../../src'; import payload from '../../src';

View File

@@ -1,69 +0,0 @@
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil';
import { initPayloadTest } from '../../helpers/configHelpers';
import { firstRegister } from '../helpers';
import { slug } from './config';
/**
* TODO: Auth
* change password
* unlock
* generate api key
* log out
*/
const { beforeAll, describe } = test;
let url: AdminUrlUtil;
describe('authentication', () => {
let page: Page;
beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadTest({
__dirname,
init: {
local: false,
},
});
// await clearDocs(); // Clear any seeded data from onInit
url = new AdminUrlUtil(serverURL, slug);
const context = await browser.newContext();
page = await context.newPage();
await firstRegister({ page, serverURL });
});
describe('Authentication', () => {
test('should login and logout', () => {
expect(1).toEqual(1);
});
test('should logout', () => {
expect(1).toEqual(1);
});
test('should allow change password', () => {
expect(1).toEqual(1);
});
test('should reset password', () => {
expect(1).toEqual(1);
});
test('should lockout after reaching max login attempts', () => {
expect(1).toEqual(1);
});
test('should prevent login for locked user', () => {
expect(1).toEqual(1);
});
test('should unlock user', () => {
expect(1).toEqual(1);
});
test('should not login without verify', () => {
expect(1).toEqual(1);
});
test('should allow generate api keys', () => {
expect(1).toEqual(1);
});
});
});

View File

@@ -1,23 +0,0 @@
import merge from 'deepmerge';
import { buildConfig as buildPayloadConfig } from '../../src/config/build';
import type { Config, SanitizedConfig } from '../../src/config/types';
export function buildConfig(overrides?: Partial<Config>): SanitizedConfig {
const baseConfig: Config = {
typescript: {
outputFile: process.env.PAYLOAD_TS_OUTPUT_PATH,
},
};
if (process.env.NODE_ENV === 'test') {
baseConfig.admin = {
webpack: (config) => ({
...config,
cache: {
type: 'memory',
},
}),
};
}
return buildPayloadConfig(merge(baseConfig, overrides || {}));
}

View File

@@ -1,8 +1,8 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import wait from '../../../src/utilities/wait'; import wait from '../../src/utilities/wait';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import { firstRegister } from '../helpers'; import { firstRegister } from '../helpers';
import { slug } from './config'; import { slug } from './config';

View File

@@ -1,7 +1,7 @@
import type { CollectionConfig } from '../../../src/collections/config/types'; import type { CollectionConfig } from '../../../src/collections/config/types';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
import { devUser } from '../../credentials'; import { devUser } from '../credentials';
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
export const slug = 'fields-relationship'; export const slug = 'fields-relationship';

View File

@@ -1,9 +1,9 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import payload from '../../../src'; import payload from '../../src';
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E, initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers'; import { login, saveDocAndAssert } from '../helpers';
import type { import type {
FieldsRelationship as CollectionWithRelationships, FieldsRelationship as CollectionWithRelationships,
@@ -19,7 +19,7 @@ import {
relationWithTitleSlug, relationWithTitleSlug,
slug, slug,
} from './config'; } from './config';
import wait from '../../../src/utilities/wait'; import wait from '../../src/utilities/wait';
const { beforeAll, describe } = test; const { beforeAll, describe } = test;

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../../../src/collections/config/types'; import type { CollectionConfig } from '../../../../src/collections/config/types';
const ArrayFields: CollectionConfig = { const ArrayFields: CollectionConfig = {
slug: 'array-fields', slug: 'array-fields',

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../../../src/collections/config/types'; import type { CollectionConfig } from '../../../../src/collections/config/types';
const BlockFields: CollectionConfig = { const BlockFields: CollectionConfig = {
slug: 'block-fields', slug: 'block-fields',

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../../../src/collections/config/types'; import type { CollectionConfig } from '../../../../src/collections/config/types';
const CollapsibleFields: CollectionConfig = { const CollapsibleFields: CollectionConfig = {
slug: 'collapsible-fields', slug: 'collapsible-fields',

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../../../src/collections/config/types'; import type { CollectionConfig } from '../../../../src/collections/config/types';
const RichTextFields: CollectionConfig = { const RichTextFields: CollectionConfig = {
slug: 'rich-text-fields', slug: 'rich-text-fields',

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../../../src/collections/config/types'; import type { CollectionConfig } from '../../../../src/collections/config/types';
const SelectFields: CollectionConfig = { const SelectFields: CollectionConfig = {
slug: 'select-fields', slug: 'select-fields',

View File

@@ -1,4 +1,4 @@
import type { CollectionConfig } from '../../../../../src/collections/config/types'; import type { CollectionConfig } from '../../../../src/collections/config/types';
const TextFields: CollectionConfig = { const TextFields: CollectionConfig = {
slug: 'text-fields', slug: 'text-fields',

View File

@@ -1,5 +1,5 @@
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
import { devUser } from '../../credentials'; import { devUser } from '../credentials';
import ArrayFields, { arrayDoc } from './collections/Array'; import ArrayFields, { arrayDoc } from './collections/Array';
import BlockFields, { blocksDoc } from './collections/Blocks'; import BlockFields, { blocksDoc } from './collections/Blocks';
import CollapsibleFields, { collapsibleDoc } from './collections/Collapsible'; import CollapsibleFields, { collapsibleDoc } from './collections/Collapsible';

View File

@@ -1,7 +1,7 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login } from '../helpers'; import { login } from '../helpers';
import { textDoc } from './collections/Text'; import { textDoc } from './collections/Text';

View File

@@ -1,7 +1,7 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect } from '@playwright/test'; import { expect } from '@playwright/test';
import wait from '../../src/utilities/wait'; import wait from '../src/utilities/wait';
import { devUser } from '../credentials'; import { devUser } from './credentials';
type FirstRegisterArgs = { type FirstRegisterArgs = {
page: Page, page: Page,

View File

@@ -28,6 +28,7 @@ export async function initPayloadTest(options: Options): Promise<{ serverURL: st
...options.init || {}, ...options.init || {},
}; };
process.env.PAYLOAD_DISABLE_ADMIN = 'true';
process.env.PAYLOAD_CONFIG_PATH = path.resolve(options.__dirname, './config.ts'); process.env.PAYLOAD_CONFIG_PATH = path.resolve(options.__dirname, './config.ts');
const port = await getPort(); const port = await getPort();

View File

@@ -1,19 +0,0 @@
import merge from 'deepmerge';
import { buildConfig as buildPayloadConfig } from '../../src/config/build';
import type { Config, SanitizedConfig } from '../../src/config/types';
export function buildConfig(overrides?: Partial<Config>): SanitizedConfig {
const baseConfig: Config = {
typescript: {
outputFile: process.env.PAYLOAD_TS_OUTPUT_PATH,
},
};
if (process.env.NODE_ENV === 'test') {
baseConfig.admin = {
disable: true,
};
}
return buildPayloadConfig(merge(baseConfig, overrides || {}));
}

View File

@@ -1,438 +0,0 @@
import fs from 'fs';
import path from 'path';
import FormData from 'form-data';
import { GraphQLClient } from 'graphql-request';
import { promisify } from 'util';
import getConfig from '../../config/load';
import { email, password } from '../../mongoose/testCredentials';
const stat = promisify(fs.stat);
require('isomorphic-fetch');
const config = getConfig();
const api = `${config.serverURL}${config.routes.api}`;
let client;
let token;
let headers;
describe('Collections - Uploads', () => {
beforeAll(async (done) => {
const response = await fetch(`${api}/admins/login`, {
body: JSON.stringify({
email,
password,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'post',
});
const data = await response.json();
({ token } = data);
headers = {
Authorization: `JWT ${token}`,
};
done();
});
describe('REST', () => {
const mediaDir = path.join(__dirname, '../../../demo', 'media');
beforeAll(async () => {
// Clear demo/media directory
const mediaDirExists = await fileExists(mediaDir);
if (!mediaDirExists) return;
fs.readdir(mediaDir, (err, files) => {
if (err) throw err;
// eslint-disable-next-line no-restricted-syntax
for (const file of files) {
fs.unlink(path.join(mediaDir, file), (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
}
});
});
describe('create', () => {
it('creates', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/image.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
// Check for files
expect(await fileExists(path.join(mediaDir, 'image.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'image-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'image-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'image-640x480.png'))).toBe(true);
// Check api response
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'image.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'image-16x16.png',
width: 16,
height: 16,
},
mobile: {
filename: 'image-320x240.png',
width: 320,
height: 240,
},
tablet: {
filename: 'image-640x480.png',
width: 640,
height: 480,
},
},
// We have a hook to check if upload sizes
// are properly bound to the Payload `req`.
// This field should be automatically set
// if they are found.
foundUploadSizes: true,
},
});
});
it('creates images that do not require all sizes', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/small.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
expect(await fileExists(path.join(mediaDir, 'small.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'small-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'small-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'small-640x480.png'))).toBe(false);
// Check api response
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'small.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'small-16x16.png',
width: 16,
height: 16,
},
},
// We have a hook to check if upload sizes
// are properly bound to the Payload `req`.
// This field should be automatically set
// if they are found.
foundUploadSizes: true,
},
});
});
it('creates media without storing a file', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/image.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/unstored-media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
// Check for files
expect(await !fileExists(path.join(mediaDir, 'image.png'))).toBe(false);
expect(await !fileExists(path.join(mediaDir, 'image-640x480.png'))).toBe(false);
// Check api response
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'image.png',
mimeType: 'image/png',
sizes: {
tablet: {
filename: 'image-640x480.png',
width: 640,
height: 480,
},
},
},
});
});
it('creates with same name', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/samename.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const firstResponse = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
expect(firstResponse.status).toBe(201);
const sameForm = new FormData();
sameForm.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/samename.png')),
);
sameForm.append('alt', 'test media');
sameForm.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: sameForm as unknown as BodyInit,
headers,
method: 'post',
});
expect(response.status).toBe(201);
const data = await response.json();
// Check for files
expect(await fileExists(path.join(mediaDir, 'samename-1.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'samename-1-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'samename-1-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'samename-1-640x480.png'))).toBe(true);
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'samename-1.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'samename-1-16x16.png',
width: 16,
height: 16,
},
mobile: {
filename: 'samename-1-320x240.png',
width: 320,
height: 240,
},
tablet: {
filename: 'samename-1-640x480.png',
width: 640,
height: 480,
},
},
},
});
});
});
it('update', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/update.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
const updateFormData = new FormData();
const newAlt = 'my new alt';
updateFormData.append('filename', data.doc.filename);
updateFormData.append('alt', newAlt);
const updateResponse = await fetch(`${api}/media/${data.doc.id}`, {
body: updateFormData as unknown as BodyInit,
headers,
method: 'put',
});
const updateResponseData = await updateResponse.json();
expect(updateResponse.status).toBe(200);
// Check that files weren't affected
expect(await fileExists(path.join(mediaDir, 'update.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'update-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'update-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'update-640x480.png'))).toBe(true);
// Check api response
expect(updateResponseData).toMatchObject({
doc: {
alt: newAlt,
filename: 'update.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'update-16x16.png',
width: 16,
height: 16,
},
mobile: {
filename: 'update-320x240.png',
width: 320,
height: 240,
},
tablet: {
filename: 'update-640x480.png',
width: 640,
height: 480,
},
maintainedAspectRatio: {},
},
},
});
});
it('delete', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/delete.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const createResponse = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const createData = await createResponse.json();
expect(createResponse.status).toBe(201);
const docId = createData.doc.id;
const response = await fetch(`${api}/media/${docId}`, {
headers,
method: 'delete',
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data.id).toBe(docId);
const imageExists = await fileExists(path.join(mediaDir, 'delete.png'));
expect(imageExists).toBe(false);
});
});
describe('GraphQL', () => {
// graphql cannot submit formData to create files, we only need to test getting relationship data on upload fields
let media;
let image;
const alt = 'alt text';
beforeAll(async (done) => {
client = new GraphQLClient(`${api}${config.routes.graphQL}`, {
headers: { Authorization: `JWT ${token}` },
});
// create media using REST
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/image.png')),
);
formData.append('alt', alt);
formData.append('locale', 'en');
const mediaResponse = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const mediaData = await mediaResponse.json();
media = mediaData.doc;
// create image that relates to media
headers['Content-Type'] = 'application/json';
const imageResponse = await fetch(`${api}/images`, {
body: JSON.stringify({
upload: media.id,
}),
headers,
method: 'post',
});
const data = await imageResponse.json();
image = data.doc;
done();
});
it('should query uploads relationship fields', async () => {
// language=graphQL
const query = `query {
Image(id: "${image.id}") {
id
upload {
alt
}
}
}`;
const response = await client.request(query);
expect(response.Image.upload.alt).toStrictEqual(alt);
});
});
});
async function fileExists(fileName: string): Promise<boolean> {
try {
await stat(fileName);
return true;
} catch (err) {
return false;
}
}

3
test/jest.setup.ts Normal file
View File

@@ -0,0 +1,3 @@
module.exports = () => {
process.env.PAYLOAD_DISABLE_ADMIN = 'true';
};

View File

@@ -1,4 +1,4 @@
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
export const slug = 'localized-posts'; export const slug = 'localized-posts';

View File

@@ -1,10 +1,10 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import payload from '../../../src'; import payload from '../../src';
import type { TypeWithTimestamps } from '../../../src/collections/config/types'; import type { TypeWithTimestamps } from '../../src/collections/config/types';
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import { firstRegister, saveDocAndAssert } from '../helpers'; import { firstRegister, saveDocAndAssert } from '../helpers';
import type { LocalizedPost } from './config'; import type { LocalizedPost } from './config';
import { slug } from './config'; import { slug } from './config';

View File

@@ -1,6 +1,6 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { buildConfig } from '../e2e/buildConfig'; import { buildConfig } from '../buildConfig';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import getFileByPath from '../../src/uploads/getFileByPath'; import getFileByPath from '../../src/uploads/getFileByPath';
@@ -17,7 +17,7 @@ export default buildConfig({
resolve: { resolve: {
...config.resolve, ...config.resolve,
alias: { alias: {
...config.resolve.alias, ...config?.resolve?.alias,
fs: mockModulePath, fs: mockModulePath,
}, },
}, },

View File

@@ -6,7 +6,7 @@ import type { Media } from './payload-types';
import payload from '../../src'; import payload from '../../src';
import { AdminUrlUtil } from '../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers'; import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../e2e/helpers'; import { login, saveDocAndAssert } from '../helpers';
import wait from '../../src/utilities/wait'; import wait from '../../src/utilities/wait';
const { beforeAll, describe } = test; const { beforeAll, describe } = test;

View File

@@ -1,7 +1,7 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import { firstRegister } from '../helpers'; import { firstRegister } from '../helpers';
import { slug } from './config'; import { slug } from './config';
@@ -51,7 +51,7 @@ describe('suite name', () => {
// }); // });
describe('feature', () => { describe('feature', () => {
test('testname', () => { it('testname', () => {
expect(1).toEqual(1); expect(1).toEqual(1);
}); });
}); });