Improvements

This commit is contained in:
ricardo 2022-04-13 21:15:11 -04:00
parent a083914649
commit 44ec5c6cfe
11 changed files with 491 additions and 174 deletions

View file

@ -2,11 +2,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable import/no-cycle */
import * as querystring from 'querystring';
// eslint-disable-next-line import/no-extraneous-dependencies
import { pick } from 'lodash';
import { In } from 'typeorm';
import { validate as uuidValidate } from 'uuid';
import { OpenAPIV3, Format } from 'express-openapi-validator/dist/framework/types';
import express = require('express');
import validator from 'validator';
import { User } from '../databases/entities/User';
import type { Role } from '../databases/entities/Role';
import { ActiveWorkflowRunner, Db, InternalHooksManager, ITelemetryUserDeletionData } from '..';
@ -23,11 +25,10 @@ interface IPaginationOffsetDecoded {
export type OperationID = 'getUsers' | 'getUser';
export const decodeCursor = (cursor: string): IPaginationOffsetDecoded => {
const data = JSON.parse(Buffer.from(cursor, 'base64').toString()) as string;
const unserializedData = querystring.decode(data) as { offset: string; limit: string };
const { offset, limit } = JSON.parse(Buffer.from(cursor, 'base64').toString());
return {
offset: parseInt(unserializedData.offset, 10),
limit: parseInt(unserializedData.limit, 10),
offset,
limit,
};
};
@ -213,7 +214,7 @@ export async function transferWorkflowsAndCredentials(data: {
{ user: data.fromUser },
{ user: data.toUser },
);
await transactionManager.delete(User, { id: data.fromUser });
await transactionManager.delete(User, { id: data.fromUser.id });
});
}
@ -258,7 +259,7 @@ async function deleteWorkflowsAndCredentials(data: { fromUser: User }): Promise<
const ownedWorkflows = await Promise.all(sharedWorkflows.map(desactiveWorkflow));
await transactionManager.remove(ownedWorkflows);
await transactionManager.remove(sharedCredentials.map(({ credentials }) => credentials));
await transactionManager.delete(User, { id: data.fromUser });
await transactionManager.delete(User, { id: data.fromUser.id });
});
}
@ -311,3 +312,41 @@ export function clean(
getSelectableProperties('user').concat(options?.includeRole ? ['globalRole'] : []),
);
}
export async function authenticationHandler(
req: express.Request,
scopes: any,
schema: OpenAPIV3.ApiKeySecurityScheme,
): Promise<boolean> {
const apiKey = req.headers[schema.name.toLowerCase()];
const user = await Db.collections.User?.findOne({
where: {
apiKey,
},
relations: ['globalRole'],
});
if (!user) {
return false;
}
req.user = user;
return true;
}
export function specFormats(): Format[] {
return [
{
name: 'email',
type: 'string',
validate: (email: string) => validator.isEmail(email),
},
{
name: 'identifier',
type: 'string',
validate: (identifier: string) =>
validator.isUUID(identifier) || validator.isEmail(identifier),
},
];
}

View file

@ -1,4 +1,5 @@
// eslint-disable-next-line import/no-cycle
import { publicApiController as publicApiControllerV1 } from './v1';
/* eslint-disable import/no-cycle */
import { publicApiControllerV1 } from './v1';
import { publicApiControllerV2 } from './v2';
export const publicApi = [publicApiControllerV1()];
export const publicApi = [publicApiControllerV1, publicApiControllerV2];

View file

@ -8,7 +8,9 @@
import express = require('express');
import config = require('../../config');
import type { UserRequest } from '../requests';
import { decodeCursor } from './helpers';
import * as UserManagementMailer from '../UserManagement/email/UserManagementMailer';
import { decodeCursor, getGlobalMemberRole } from './helpers';
type Role = 'owner' | 'member';
@ -48,23 +50,6 @@ const authorize =
});
};
// move this to open api validator
// const validEmail = (
// req: UserRequest.Invite,
// res: express.Response,
// next: express.NextFunction,
// ): any => {
// // eslint-disable-next-line no-restricted-syntax
// for (const { email } of req.body) {
// if (!validator.isEmail(email)) {
// return res.status(400).json({
// message: `Request to send email invite(s) to user(s) failed because of an invalid email address: ${email}`,
// });
// }
// }
// next();
// };
const deletingOwnUser = (
req: UserRequest.Delete,
res: express.Response,
@ -96,27 +81,64 @@ const validCursor = (
res: express.Response,
next: express.NextFunction,
): any => {
let offset = 0;
let limit = 10;
if (req.query.cursor) {
const { cursor } = req.query;
try {
({ offset, limit } = decodeCursor(cursor));
const { offset, limit } = decodeCursor(cursor);
req.query.offset = offset;
req.query.limit = limit;
} catch (error) {
return res.status(400).json({
message: `invalid cursor`,
message: 'An invalid cursor was used',
});
}
}
// @ts-ignore
req.query.offset = offset;
// @ts-ignore
req.query.limit = limit;
next();
};
const getMailerInstance = async (
req: UserRequest.Invite,
res: express.Response,
next: express.NextFunction,
): Promise<any> => {
let mailer: UserManagementMailer.UserManagementMailer | undefined;
try {
mailer = await UserManagementMailer.getInstance();
req.mailer = mailer;
} catch (error) {
if (error instanceof Error) {
return res.status(500).json({
message: 'Email sending must be set up in order to request a password reset email',
});
}
}
next();
};
const globalMemberRoleSetup = async (
req: UserRequest.Invite,
res: express.Response,
next: express.NextFunction,
): Promise<any> => {
try {
const role = await getGlobalMemberRole();
req.globalMemberRole = role;
} catch (error) {
return res.status(500).json({
message: 'Members role not found in database - inconsistent state',
});
}
next();
};
export const middlewares = {
createUsers: [instanceOwnerSetup, emailSetup, authorize(['owner'])],
createUsers: [
instanceOwnerSetup,
emailSetup,
authorize(['owner']),
getMailerInstance,
globalMemberRoleSetup,
],
deleteUsers: [
instanceOwnerSetup,
deletingOwnUser,

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
@ -8,13 +9,11 @@ import express = require('express');
import { UserRequest } from '../../../../requests';
import { User } from '../../../../databases/entities/User';
import { Role } from '../../../../databases/entities/Role';
import {
clean,
deleteDataAndSendTelemetry,
getAllUsersAndCount,
getGlobalMemberRole,
encodeNextCursor,
getUser,
getUsers,
@ -24,8 +23,6 @@ import {
transferWorkflowsAndCredentials,
} from '../../../helpers';
import * as UserManagementMailer from '../../../../UserManagement/email/UserManagementMailer';
import { ResponseHelper } from '../../../..';
import { middlewares } from '../../../middlewares';
@ -36,31 +33,7 @@ export = {
ResponseHelper.send(async (req: UserRequest.Invite, res: express.Response) => {
const tokenOwnerId = req.user.id;
const emailsInBody = req.body.map((data) => data.email);
let mailer: UserManagementMailer.UserManagementMailer | undefined;
try {
mailer = await UserManagementMailer.getInstance();
} catch (error) {
if (error instanceof Error) {
throw new ResponseHelper.ResponseError(
'Email sending must be set up in order to request a password reset email',
undefined,
500,
);
}
}
let role: Role | undefined;
try {
role = await getGlobalMemberRole();
} catch (error) {
throw new ResponseHelper.ResponseError(
'Members role not found in database - inconsistent state',
undefined,
500,
);
}
const { mailer, globalMemberRole: role } = req;
const { usersToSave, pendingUsers } = await getUsersToSaveAndInvite(emailsInBody);
@ -69,40 +42,45 @@ export = {
try {
savedUsers = await saveUsersWithRole(usersToSave, role!, tokenOwnerId);
} catch (error) {
throw new ResponseHelper.ResponseError('An error occurred during user creation');
return res.status(500).json({
message: 'An error occurred during user creation',
});
}
const userstoInvite = [...savedUsers, ...pendingUsers];
await inviteUsers(userstoInvite, mailer, tokenOwnerId);
return clean(userstoInvite);
return res.json(clean(userstoInvite));
}),
],
deleteUser: [
...middlewares.deleteUsers,
async (req: UserRequest.Delete, res: express.Response): Promise<any> => {
async (req: UserRequest.Delete, res: express.Response) => {
const { identifier: idToDelete } = req.params;
const { transferId, includeRole } = req.query;
const { transferId = '', includeRole = false } = req.query;
const apiKeyUserOwner = req.user;
const users = await getUsers({
withIdentifiers: [idToDelete, transferId ?? ''],
withIdentifiers: [idToDelete, transferId],
includeRole,
});
if (!users?.length || (transferId && users.length !== 2)) {
throw new ResponseHelper.ResponseError(
'Request to delete a user failed because the ID of the user to delete and/or the ID of the transferee were not found in DB',
undefined,
400,
);
if (!users?.length || (transferId !== '' && users.length !== 2)) {
return res.status(400).json({
message:
'Request to delete a user failed because the ID of the user to delete and/or the ID of the transferee were not found in DB',
});
}
const userToDelete = users?.find((user) => user.id === req.params.identifier) as User;
const userToDelete = users?.find(
(user) => user.id === req.params.identifier || user.email === req.params.identifier,
) as User;
if (transferId) {
const transferee = users?.find((user) => user.id === transferId) as User;
const transferee = users?.find(
(user) => user.id === transferId || user.email === transferId,
) as User;
await transferWorkflowsAndCredentials({
fromUser: userToDelete,
@ -118,34 +96,30 @@ export = {
transferId,
});
return clean(userToDelete);
return clean(userToDelete, { includeRole });
},
],
getUser: [
...middlewares.getUser,
// @ts-ignore
ResponseHelper.send(async (req: UserRequest.Get, res: express.Response) => {
const { includeRole } = req.query;
async (req: UserRequest.Get, res: express.Response) => {
const { includeRole = false } = req.query;
const { identifier } = req.params;
const user = await getUser({ withIdentifier: identifier, includeRole });
if (!user) {
throw new ResponseHelper.ResponseError(
`Could not find user with identifier: ${identifier}`,
undefined,
404,
);
return res.status(404).json({
message: `Could not find user with identifier: ${identifier}`,
});
}
return clean(user, { includeRole });
}, true),
return res.json(clean(user, { includeRole }));
},
],
getUsers: [
...middlewares.getUsers,
// @ts-ignore
ResponseHelper.send(async (req: UserRequest.Get, res: express.Response) => {
const { offset, limit, includeRole = false } = req.query;
async (req: UserRequest.Get, res: express.Response) => {
const { offset = 0, limit = 100, includeRole = false } = req.query;
const [users, count] = await getAllUsersAndCount({
includeRole,
@ -153,10 +127,10 @@ export = {
offset,
});
return {
users: clean(users, { includeRole }),
return res.json({
data: clean(users, { includeRole }),
nextCursor: encodeNextCursor(offset, limit, count),
};
}, true),
});
},
],
};

View file

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
@ -8,62 +10,39 @@ import path = require('path');
import express = require('express');
import { HttpError, OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';
import { HttpError } from 'express-openapi-validator/dist/framework/types';
import { Db } from '../..';
import { authenticationHandler, specFormats } from '../helpers';
export const publicApiController = (): express.Router => {
const openApiSpec = path.join(__dirname, 'openapi.yml');
export const publicApiControllerV1 = express.Router();
const apiController = express.Router();
const openApiSpec = path.join(__dirname, 'openapi.yml');
apiController.use('/v1/spec', express.static(openApiSpec));
publicApiControllerV1.use('/v1/spec', express.static(openApiSpec));
apiController.use('/v1', express.json());
publicApiControllerV1.use('/v1', express.json());
apiController.use(
'/v1',
OpenApiValidator.middleware({
apiSpec: openApiSpec,
operationHandlers: path.join(__dirname),
validateRequests: true,
validateApiSpec: true,
validateSecurity: {
handlers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
ApiKeyAuth: async (req, scopes, schema: OpenAPIV3.ApiKeySecurityScheme) => {
const apiKey = req.headers[schema.name.toLowerCase()];
const user = await Db.collections.User?.findOne({
where: {
apiKey,
},
relations: ['globalRole'],
});
if (!user) {
return false;
}
req.user = user;
return true;
},
},
publicApiControllerV1.use(
'/v1',
OpenApiValidator.middleware({
apiSpec: openApiSpec,
operationHandlers: path.join(__dirname, '..'),
validateRequests: true,
validateApiSpec: true,
formats: specFormats(),
validateSecurity: {
handlers: {
ApiKeyAuth: authenticationHandler,
},
}),
);
// add error handler
// @ts-ignore
apiController.use(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction) => {
return res.status(error.status || 400).json({
message: error.message,
// errors: error.errors,
});
},
);
}),
);
return apiController;
};
// error handler
publicApiControllerV1.use(
(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction) => {
return res.status(error.status || 400).json({
message: error.message,
});
},
);

View file

@ -25,7 +25,7 @@ paths:
/users:
get:
x-eov-operation-id: getUsers
x-eov-operation-handler: routes/Users
x-eov-operation-handler: v1/handlers/Users
tags:
- users
summary: Retrieve all users
@ -73,7 +73,7 @@ paths:
$ref: '#/components/schemas/Error'
post:
x-eov-operation-id: createUsers
x-eov-operation-handler: routes/Users
x-eov-operation-handler: v1/handlers/Users
tags:
- users
summary: Invite a user
@ -108,7 +108,7 @@ paths:
/users/{identifier}:
get:
x-eov-operation-id: getUser
x-eov-operation-handler: routes/Users
x-eov-operation-handler: v1/handlers/Users
tags:
- users
summary: Get user by ID/Email
@ -123,6 +123,7 @@ paths:
explode: false
schema:
type: string
format: identifier
- name: includeRole
in: query
required: false
@ -146,7 +147,7 @@ paths:
$ref: '#/components/schemas/Error'
delete:
x-eov-operation-id: deleteUser
x-eov-operation-handler: routes/Users
x-eov-operation-handler: v1/handlers/Users
tags:
- users
summary: Delete user by ID/Email
@ -161,6 +162,7 @@ paths:
explode: false
schema:
type: string
format: identifier
- name: transferId
in: query
description: ID of the user to transfer workflows and credentials to.
@ -175,7 +177,7 @@ paths:
style: form
explode: true
schema:
type: string
type: boolean
example: true
responses:
"200":
@ -235,6 +237,7 @@ components:
example: 123e4567-e89b-12d3-a456-426614174000
email:
type: string
format: email
example: jhon.doe@company.com
firstName:
maxLength: 32

View file

@ -0,0 +1,37 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unused-vars */
import express = require('express');
import { UserRequest } from '../../../../requests';
import { getUser } from '../../../helpers';
import { ResponseHelper } from '../../../..';
import { middlewares } from '../../../middlewares';
export = {
getUser: [
...middlewares.getUser,
// @ts-ignore
ResponseHelper.send(async (req: UserRequest.Get, res: express.Response) => {
const { includeRole = false } = req.query;
const { identifier } = req.params;
const user = await getUser({ withIdentifier: identifier, includeRole });
if (!user) {
throw new ResponseHelper.ResponseError(
`Could not find user with identifier: ${identifier}`,
undefined,
404,
);
}
return user;
}, true),
],
};

View file

@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
/* eslint-disable import/no-cycle */
import * as OpenApiValidator from 'express-openapi-validator';
import path = require('path');
import express = require('express');
import { HttpError } from 'express-openapi-validator/dist/framework/types';
import { authenticationHandler } from '../helpers';
export const publicApiControllerV2 = express.Router();
const openApiSpec = path.join(__dirname, 'openapi.yml');
publicApiControllerV2.use('/v2/spec', express.static(openApiSpec));
publicApiControllerV2.use('/v2', express.json());
publicApiControllerV2.use(
'/v2',
OpenApiValidator.middleware({
apiSpec: openApiSpec,
operationHandlers: path.join(__dirname, '..'),
validateRequests: true,
validateApiSpec: true,
validateSecurity: {
handlers: {
ApiKeyAuth: authenticationHandler,
},
},
}),
);
// error handler
publicApiControllerV2.use(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction) => {
return res.status(error.status || 400).json({
message: error.message,
// errors: error.errors,
});
},
);

View file

@ -0,0 +1,231 @@
---
openapi: 3.0.0
info:
title: Public n8n API
description: n8n Public API
termsOfService: https://n8n.io/legal/terms
contact:
email: hello@n8n.io
license:
name: Apache 2.0 with Commons Clause
url: https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md
version: 1.0.0
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: /api/v2
tags:
- name: users
description: Operations about user
externalDocs:
description: Find out more about our store
url: http://swagger.io
paths:
/users:
get:
x-eov-operation-id: getUsers
x-eov-operation-handler: v1/handlers/Users
tags:
- users
summary: Retrieve all users
description: Retrieve all users from your instance. Only available for the instance owner.
parameters:
- name: limit
in: query
description: The maximum number of items to return
required: false
style: form
explode: true
schema:
type: number
example: 100
default: 100
- name: cursor
in: query
description: Paginate through users by setting the cursor parameter to a nextCursor attribute returned by a previous request's response_metadata. Default value fetches the first "page" of the collection. See pagination for more detail.
required: false
style: form
explode: true
schema:
type: string
example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
- name: includeRole
in: query
required: false
style: form
explode: true
schema:
type: boolean
example: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/inline_response_200'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users/{identifier}:
get:
x-eov-operation-id: getUser
x-eov-operation-handler: v2/handlers/Users
tags:
- users
summary: Get user by ID/Email
description: Retrieve a user from your instance. Only available for the instance owner.
operationId: getUser
parameters:
- name: identifier
in: path
description: The ID or email of the user
required: true
style: simple
explode: false
schema:
type: string
- name: includeRole
in: query
required: false
style: form
explode: true
schema:
type: boolean
example: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
InputValidationError:
required:
- code
- description
- message
type: object
properties:
code:
type: string
message:
type: string
errors:
$ref: '#/components/schemas/Errors'
Error:
required:
- code
- description
- message
type: object
properties:
code:
type: string
message:
type: string
description:
type: string
Errors:
type: array
items:
$ref: '#/components/schemas/Error'
Users:
type: array
items:
$ref: '#/components/schemas/User'
User:
required:
- email
type: object
properties:
id:
type: string
readOnly: true
example: 123e4567-e89b-12d3-a456-426614174000
email:
type: string
example: jhon.doe@company.com
firstName:
maxLength: 32
type: string
description: User's first name
readOnly: true
example: jhon
lastName:
maxLength: 32
type: string
description: User's last name
readOnly: true
example: doe
pending:
type: boolean
description: Whether the user finished setting up the invitation or not
readOnly: true
createdAt:
type: string
description: Time the user was created
format: date-time
readOnly: true
updatedAt:
type: string
description: Last time the user was updaded
format: date-time
readOnly: true
inline_response_200:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
nextCursor:
type: string
description: Paginate through users by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
nullable: true
example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
responses:
NotFound:
description: The specified resource was not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
UnprocessableEntity:
description: Unprocessable Entity
content:
application/json:
schema:
$ref: '#/components/schemas/InputValidationError'
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-N8N-API-KEY
security:
- ApiKeyAuth: []

View file

@ -11,8 +11,10 @@ import {
} from 'n8n-workflow';
import { User } from './databases/entities/User';
import { Role } from './databases/entities/Role';
import type { IExecutionDeleteFilter, IWorkflowDb } from '.';
import type { PublicUser } from './UserManagement/Interfaces';
import * as UserManagementMailer from './UserManagement/email/UserManagementMailer';
export type AuthlessRequest<
RouteParams = {},
@ -28,6 +30,8 @@ export type AuthenticatedRequest<
RequestQuery = {},
> = express.Request<RouteParams, ResponseBody, RequestBody, RequestQuery> & {
user: User;
mailer?: UserManagementMailer.UserManagementMailer;
globalMemberRole?: Role;
};
// ----------------------------------
@ -209,7 +213,7 @@ export declare namespace UserRequest {
{ id: string; email: string; identifier: string },
{},
{},
{ limit: number; offset: number; cursor: string; includeRole: boolean }
{ limit?: number; offset?: number; cursor?: string; includeRole?: boolean }
>;
export type Reinvite = AuthenticatedRequest<{ id: string }>;

View file

@ -10,7 +10,6 @@ import { Role } from '../../../src/databases/entities/Role';
import {
randomApiKey,
randomEmail,
randomInvalidPassword,
randomName,
randomValidPassword,
} from '../shared/random';
@ -18,8 +17,6 @@ import {
import * as utils from '../shared/utils';
import * as testDb from '../shared/testDb';
// import * from './../../../src/PublicApi/helpers'
let app: express.Application;
let testDbName = '';
let globalOwnerRole: Role;
@ -55,14 +52,6 @@ beforeEach(async () => {
await testDb.truncate(['SharedCredentials', 'SharedWorkflow'], testDbName);
await testDb.truncate(['User', 'Workflow', 'Credentials'], testDbName);
// jest.isolateModules(() => {
// jest.mock('../../../config');
// jest.mock('./../../../src/PublicApi/helpers', () => ({
// ...jest.requireActual('./../../../src/PublicApi/helpers'),
// connectionName: jest.fn(() => testDbName),
// }));
// });
await testDb.createUser({
id: INITIAL_TEST_USER.id,
email: INITIAL_TEST_USER.email,
@ -107,7 +96,6 @@ test('GET /users should fail due to invalid API Key', async () => {
});
test('GET /users should fail due to member trying to access owner only endpoint', async () => {
config.set('userManagement.isInstanceOwnerSetUp', true);
const member = await testDb.createUser();
@ -125,16 +113,7 @@ test('GET /users should fail due no instance owner not setup', async () => {
const authOwnerAgent = utils.createAgent(app, { apiPath: 'public', auth: true, user: owner });
// console.log(authOwnerAgent);
const response = await authOwnerAgent.get('/v1/users');
// const response2 = await authOwnerAgent.get('/v1/spec');
// const response3 = await authOwnerAgent.get('/v1/hello');
// console.log(response.body);
// console.log(response.statusCode);
// console.log(authOwnerAgent.app);
expect(response.statusCode).toBe(500);
});