Skip to content

Commit

Permalink
Update test-utils to support network request based tests (#4874)
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie authored Feb 18, 2021
1 parent de3e982 commit 7ae67b8
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 62 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-keys-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystonejs/test-utils': major
---

Updated `setupFromConfig` to support running tests with `networkedGraphqlRequest`.
5 changes: 5 additions & 0 deletions .changeset/polite-apples-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': minor
---

Exported the `createExpressServer` function to support running isolated unit tests against the GraphQL API.
2 changes: 1 addition & 1 deletion examples-next/ecommerce/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const databaseURL = process.env.DATABASE_URL || 'mongodb://localhost/keystone-si

const sessionConfig = {
maxAge: 60 * 60 * 24 * 360, // How long they stay signed in?
secret: process.env.COOKIE_SECRET!,
secret: process.env.COOKIE_SECRET || 'this secret should only be used in testing',
};

const { withAuth } = createAuth({
Expand Down
1 change: 1 addition & 0 deletions packages-next/keystone/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { createSystem } from './lib/createSystem';
export { createExpressServer } from './lib/createExpressServer';
export { initConfig } from './lib/initConfig';
18 changes: 13 additions & 5 deletions packages/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { KnexAdapter } from '@keystonejs/adapter-knex';
import { MongooseAdapter } from '@keystonejs/adapter-mongoose';
// @ts-ignore
import { PrismaAdapter } from '@keystonejs/adapter-prisma';
import { initConfig, createSystem } from '@keystone-next/keystone';
import { initConfig, createSystem, createExpressServer } from '@keystone-next/keystone';
import type { KeystoneConfig, BaseKeystone, KeystoneContext } from '@keystone-next/types';

export type AdapterName = 'mongoose' | 'knex' | 'prisma_postgresql';
Expand Down Expand Up @@ -63,10 +63,18 @@ async function setupFromConfig({
const adapterArgs = await argGenerator[adapterName]();
config.db = { adapter: adapterName, ...adapterArgs };
}
config.ui = { isDisabled: true };
config = initConfig(config);

const { keystone, createContext } = createSystem(config, path.resolve('.keystone'), '');
return { keystone, context: createContext().sudo() };
const { keystone, createContext, graphQLSchema } = createSystem(
config,
path.resolve('.keystone'),
''
);

const app = await createExpressServer(config, graphQLSchema, createContext, true, '');

return { keystone, context: createContext().sudo(), app };
}

async function setupServer({
Expand Down Expand Up @@ -104,7 +112,7 @@ async function setupServer({
const apps = [
new GraphQLApp({
schemaName,
apiPath: '/admin/api',
apiPath: '/api/graphql',
graphiqlPath: '/admin/graphiql',
apollo: {
tracing: true,
Expand Down Expand Up @@ -144,7 +152,7 @@ function networkedGraphqlRequest({
Object.entries(headers).forEach(([key, value]) => request.set(key, value));

return request
.post('/admin/api', { query, variables, operationName })
.post('/api/graphql', { query, variables, operationName })
.then((res: ServerResponse & { text: string }) => {
expect(res.statusCode).toBe(expectedStatusCode);
return { ...JSON.parse(res.text), res };
Expand Down
116 changes: 61 additions & 55 deletions tests/api-tests/auth-header.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
const { PasswordAuthStrategy } = require('@keystonejs/auth-password');
const { Text, Password, DateTime } = require('@keystonejs/fields');
const { multiAdapterRunners, networkedGraphqlRequest } = require('@keystonejs/test-utils');
const { setupServer } = require('@keystonejs/test-utils');
import Iron from '@hapi/iron';
const { text, timestamp, password } = require('@keystone-next/fields');
const { createSchema, list } = require('@keystone-next/keystone/schema');
const { statelessSessions, withItemData } = require('@keystone-next/keystone/session');
const { createAuth } = require('@keystone-next/auth');
const {
multiAdapterRunners,
setupFromConfig,
networkedGraphqlRequest,
} = require('@keystonejs/test-utils');
const { createItems } = require('@keystonejs/server-side-graphql-client');

const initialData = {
Expand All @@ -23,56 +29,54 @@ const initialData = {
],
};

const COOKIE_SECRET = 'qwerty';
const defaultAccess = ({ authentication: { item } }) => !!item;
const COOKIE_SECRET = 'qwertyuiopasdfghjlkzxcvbmnm1234567890';
const defaultAccess = context => !!context.session?.item;

const auth = createAuth({ listKey: 'User', identityField: 'email', secretField: 'password' });

function setupKeystone(adapterName) {
return setupServer({
return setupFromConfig({
adapterName,
createLists: keystone => {
keystone.createList('Post', {
fields: {
title: { type: Text },
postedAt: { type: DateTime },
},
});

keystone.createList('User', {
fields: {
name: { type: Text },
email: { type: Text },
password: { type: Password },
config: auth.withAuth(
createSchema({
lists: {
Post: list({
fields: {
title: text(),
postedAt: timestamp(),
},
}),
User: list({
fields: {
name: text(),
email: text(),
password: password(),
},
access: {
create: defaultAccess,
read: defaultAccess,
update: defaultAccess,
delete: defaultAccess,
auth: true,
},
}),
},
access: {
create: defaultAccess,
read: defaultAccess,
update: defaultAccess,
delete: defaultAccess,
auth: true,
},
});

keystone.createAuthStrategy({
type: PasswordAuthStrategy,
list: 'User',
});
},
keystoneOptions: {
cookieSecret: COOKIE_SECRET,
defaultAccess: {
list: defaultAccess,
},
},
session: withItemData(statelessSessions({ secret: COOKIE_SECRET }), { User: 'id' }),
})
),
});
}

function login(app, email, password) {
return networkedGraphqlRequest({
app,
query: `
mutation($email: String, $password: String) {
mutation($email: String!, $password: String!) {
authenticateUserWithPassword(email: $email, password: $password) {
token
... on UserAuthenticationWithPasswordSuccess {
sessionToken
item { id }
}
}
}
`,
Expand All @@ -87,10 +91,10 @@ multiAdapterRunners().map(({ runner, adapterName }) =>
describe('Auth testing', () => {
test(
'Gives access denied when not logged in',
runner(setupKeystone, async ({ keystone, app }) => {
runner(setupKeystone, async ({ context, app }) => {
// seed the db
for (const [listKey, items] of Object.entries(initialData)) {
await createItems({ keystone, listKey, items });
await createItems({ context, listKey, items });
}
const { data, errors } = await networkedGraphqlRequest({
app,
Expand All @@ -102,23 +106,24 @@ multiAdapterRunners().map(({ runner, adapterName }) =>
);

describe('logged in', () => {
test(
// eslint-disable-next-line jest/no-disabled-tests
test.skip(
'Allows access with bearer token',
runner(setupKeystone, async ({ keystone, app }) => {
runner(setupKeystone, async ({ context, app }) => {
for (const [listKey, items] of Object.entries(initialData)) {
await createItems({ keystone, listKey, items });
await createItems({ context, listKey, items });
}
const { token } = await login(
const { sessionToken } = await login(
app,
initialData.User[0].data.email,
initialData.User[0].data.password
);

expect(token).toBeTruthy();
expect(sessionToken).toBeTruthy();
const { data, errors } = await networkedGraphqlRequest({
app,
headers: {
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${sessionToken}`,
},
query: '{ allUsers { id } }',
});
Expand All @@ -131,22 +136,23 @@ multiAdapterRunners().map(({ runner, adapterName }) =>

test(
'Allows access with cookie',
runner(setupKeystone, async ({ keystone, app }) => {
runner(setupKeystone, async ({ context, app }) => {
for (const [listKey, items] of Object.entries(initialData)) {
await createItems({ keystone, listKey, items });
await createItems({ context, listKey, items });
}
const { token } = await login(
const { sessionToken, item } = await login(
app,
initialData.User[0].data.email,
initialData.User[0].data.password
);

expect(token).toBeTruthy();
expect(sessionToken).toBeTruthy();

const sealedData = await Iron.seal({ item }, COOKIE_SECRET, Iron.defaults);
const { data, errors } = await networkedGraphqlRequest({
app,
headers: {
Cookie: `keystone.sid=s:${token}`,
Cookie: `keystonejs-session=${sealedData}`,
},
query: '{ allUsers { id } }',
});
Expand Down
2 changes: 1 addition & 1 deletion tests/api-tests/hooks/auth-hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const runTestInServer = async (app, testFn) => {
try {
const _runQuery = async (query, agent) => {
const { port } = server.address();
const url = `http://localhost:${port}/admin/api`;
const url = `http://localhost:${port}/api/graphql`;
agent = agent || superagent.agent();
const { res } = await agent.post(url).set('Accept', 'application/json').send({ query });
// console.log(res);
Expand Down
2 changes: 2 additions & 0 deletions tests/api-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"repository": "https://github.com/keystonejs/keystone/tree/master/tests/api-tests",
"homepage": "https://github.com/keystonejs/keystone",
"devDependencies": {
"@hapi/iron": "^5.1.4",
"@keystone-next/auth": "*",
"@keystone-next/fields": "*",
"@keystone-next/keystone": "*",
"@keystonejs/adapter-knex": "*",
Expand Down

1 comment on commit 7ae67b8

@vercel
Copy link

@vercel vercel bot commented on 7ae67b8 Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.