mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Chore: Server: Refactor error codes
This commit is contained in:
parent
d532b9dffc
commit
60c25f88cf
@ -3,7 +3,7 @@ import { ItemType, databaseSchema, Uuid, Item, ShareType, Share, ChangeType, Use
|
|||||||
import { defaultPagination, paginateDbQuery, PaginatedResults, Pagination } from './utils/pagination';
|
import { defaultPagination, paginateDbQuery, PaginatedResults, Pagination } from './utils/pagination';
|
||||||
import { isJoplinItemName, isJoplinResourceBlobPath, linkedResourceIds, serializeJoplinItem, unserializeJoplinItem } from '../utils/joplinUtils';
|
import { isJoplinItemName, isJoplinResourceBlobPath, linkedResourceIds, serializeJoplinItem, unserializeJoplinItem } from '../utils/joplinUtils';
|
||||||
import { ModelType } from '@joplin/lib/BaseModel';
|
import { ModelType } from '@joplin/lib/BaseModel';
|
||||||
import { ApiError, ErrorCode, ErrorConflict, ErrorForbidden, ErrorPayloadTooLarge, ErrorUnprocessableEntity } from '../utils/errors';
|
import { ApiError, CustomErrorCode, ErrorConflict, ErrorForbidden, ErrorPayloadTooLarge, ErrorUnprocessableEntity, ErrorCode } from '../utils/errors';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ChangePreviousItem } from './ChangeModel';
|
import { ChangePreviousItem } from './ChangeModel';
|
||||||
import { unique } from '../utils/array';
|
import { unique } from '../utils/array';
|
||||||
@ -311,7 +311,7 @@ export default class ItemModel extends BaseModel<Item> {
|
|||||||
try {
|
try {
|
||||||
content = await fromDriver.read(item.id, { models: this.models() });
|
content = await fromDriver.read(item.id, { models: this.models() });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === ErrorCode.NotFound) {
|
if (error.code === CustomErrorCode.NotFound) {
|
||||||
logger.info(`Could not process item, because content was deleted: ${item.id}`);
|
logger.info(`Could not process item, because content was deleted: ${item.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -789,7 +789,7 @@ export default class ItemModel extends BaseModel<Item> {
|
|||||||
// that shared folder. It returns null otherwise.
|
// that shared folder. It returns null otherwise.
|
||||||
public async joplinItemSharedRootInfo(jopId: string): Promise<SharedRootInfo | null> {
|
public async joplinItemSharedRootInfo(jopId: string): Promise<SharedRootInfo | null> {
|
||||||
const path = await this.joplinItemPath(jopId);
|
const path = await this.joplinItemPath(jopId);
|
||||||
if (!path.length) throw new ApiError(`Cannot retrieve path for item: ${jopId}`, null, 'noPathForItem');
|
if (!path.length) throw new ApiError(`Cannot retrieve path for item: ${jopId}`, null, ErrorCode.NoPathForItem);
|
||||||
const rootFolderItem = path[path.length - 1];
|
const rootFolderItem = path[path.length - 1];
|
||||||
const share = await this.models().share().itemShare(ShareType.Folder, rootFolderItem.id);
|
const share = await this.models().share().itemShare(ShareType.Folder, rootFolderItem.id);
|
||||||
if (!share) return null;
|
if (!share) return null;
|
||||||
|
@ -2,7 +2,7 @@ import BaseModel, { UuidType } from './BaseModel';
|
|||||||
import { Uuid } from '../services/database/types';
|
import { Uuid } from '../services/database/types';
|
||||||
import { LockType, Lock, LockClientType, defaultLockTtl, activeLock } from '@joplin/lib/services/synchronizer/LockHandler';
|
import { LockType, Lock, LockClientType, defaultLockTtl, activeLock } from '@joplin/lib/services/synchronizer/LockHandler';
|
||||||
import { Value } from './KeyValueModel';
|
import { Value } from './KeyValueModel';
|
||||||
import { ErrorConflict, ErrorUnprocessableEntity } from '../utils/errors';
|
import { ErrorConflict, ErrorUnprocessableEntity, ErrorCode } from '../utils/errors';
|
||||||
import uuidgen from '../utils/uuidgen';
|
import uuidgen from '../utils/uuidgen';
|
||||||
|
|
||||||
export default class LockModel extends BaseModel<Lock> {
|
export default class LockModel extends BaseModel<Lock> {
|
||||||
@ -59,7 +59,7 @@ export default class LockModel extends BaseModel<Lock> {
|
|||||||
const exclusiveLock = activeLock(locks, new Date(), this.lockTtl, LockType.Exclusive);
|
const exclusiveLock = activeLock(locks, new Date(), this.lockTtl, LockType.Exclusive);
|
||||||
|
|
||||||
if (exclusiveLock) {
|
if (exclusiveLock) {
|
||||||
throw new ErrorConflict(`Cannot acquire lock because there is already an exclusive lock for client: ${exclusiveLock.clientType} #${exclusiveLock.clientId}`, 'hasExclusiveLock');
|
throw new ErrorConflict(`Cannot acquire lock because there is already an exclusive lock for client: ${exclusiveLock.clientType} #${exclusiveLock.clientId}`, ErrorCode.HasExclusiveSyncLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncLock = activeLock(locks, new Date(), this.lockTtl, LockType.Sync, clientType, clientId);
|
const syncLock = activeLock(locks, new Date(), this.lockTtl, LockType.Sync, clientType, clientId);
|
||||||
@ -111,7 +111,7 @@ export default class LockModel extends BaseModel<Lock> {
|
|||||||
|
|
||||||
return JSON.stringify(locks);
|
return JSON.stringify(locks);
|
||||||
} else {
|
} else {
|
||||||
throw new ErrorConflict(`Cannot acquire lock because there is already an exclusive lock for client: ${exclusiveLock.clientType} #${exclusiveLock.clientId}`, 'hasExclusiveLock');
|
throw new ErrorConflict(`Cannot acquire lock because there is already an exclusive lock for client: ${exclusiveLock.clientType} #${exclusiveLock.clientId}`, ErrorCode.HasExclusiveSyncLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ export default class LockModel extends BaseModel<Lock> {
|
|||||||
if (syncLock.clientId === clientId) {
|
if (syncLock.clientId === clientId) {
|
||||||
locks = locks.filter(l => l.id !== syncLock.id);
|
locks = locks.filter(l => l.id !== syncLock.id);
|
||||||
} else {
|
} else {
|
||||||
throw new ErrorConflict(`Cannot acquire exclusive lock because there is an active sync lock for client: ${syncLock.clientType} #${syncLock.clientId}`, 'hasSyncLock');
|
throw new ErrorConflict(`Cannot acquire exclusive lock because there is an active sync lock for client: ${syncLock.clientType} #${syncLock.clientId}`, ErrorCode.HasSyncLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// database (as a binary blob). For now the driver expects that the content is
|
// database (as a binary blob). For now the driver expects that the content is
|
||||||
// stored in the same table as the items, as it originally was.
|
// stored in the same table as the items, as it originally was.
|
||||||
|
|
||||||
import { CustomError, ErrorCode } from '../../../utils/errors';
|
import { CustomError, CustomErrorCode } from '../../../utils/errors';
|
||||||
import { DatabaseConfigClient, StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
import { DatabaseConfigClient, StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
||||||
import StorageDriverBase, { Context } from './StorageDriverBase';
|
import StorageDriverBase, { Context } from './StorageDriverBase';
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export default class StorageDriverDatabase extends StorageDriverBase {
|
|||||||
|
|
||||||
// Calling code should only call this handler if the row exists, so if
|
// Calling code should only call this handler if the row exists, so if
|
||||||
// we find it doesn't, it's an error.
|
// we find it doesn't, it's an error.
|
||||||
if (!row) throw new CustomError(`No such row: ${itemId}`, ErrorCode.NotFound);
|
if (!row) throw new CustomError(`No such row: ${itemId}`, CustomErrorCode.NotFound);
|
||||||
|
|
||||||
return row.content;
|
return row.content;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { mkdirp, pathExists, readFile, remove, writeFile } from 'fs-extra';
|
import { mkdirp, pathExists, readFile, remove, writeFile } from 'fs-extra';
|
||||||
import { CustomError, ErrorCode } from '../../../utils/errors';
|
import { CustomError, CustomErrorCode } from '../../../utils/errors';
|
||||||
import { StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
import { StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
||||||
import StorageDriverBase from './StorageDriverBase';
|
import StorageDriverBase from './StorageDriverBase';
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export default class StorageDriverFs extends StorageDriverBase {
|
|||||||
const result = await readFile(this.itemPath(itemId));
|
const result = await readFile(this.itemPath(itemId));
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') throw new CustomError(`Not found: ${itemId}`, ErrorCode.NotFound);
|
if (error.code === 'ENOENT') throw new CustomError(`Not found: ${itemId}`, CustomErrorCode.NotFound);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ export default class StorageDriverFs extends StorageDriverBase {
|
|||||||
try {
|
try {
|
||||||
await remove(this.itemPath(id));
|
await remove(this.itemPath(id));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') throw new CustomError(`Not found: ${itemId}`, ErrorCode.NotFound);
|
if (error.code === 'ENOENT') throw new CustomError(`Not found: ${itemId}`, CustomErrorCode.NotFound);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CustomError, ErrorCode } from '../../../utils/errors';
|
import { CustomError, CustomErrorCode } from '../../../utils/errors';
|
||||||
import { StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
import { StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
||||||
import StorageDriverBase from './StorageDriverBase';
|
import StorageDriverBase from './StorageDriverBase';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ export default class StorageDriverMemory extends StorageDriverBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async read(itemId: string): Promise<Buffer> {
|
public async read(itemId: string): Promise<Buffer> {
|
||||||
if (!(itemId in this.data_)) throw new CustomError(`No such item: ${itemId}`, ErrorCode.NotFound);
|
if (!(itemId in this.data_)) throw new CustomError(`No such item: ${itemId}`, CustomErrorCode.NotFound);
|
||||||
return this.data_[itemId];
|
return this.data_[itemId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectsCommand, ObjectIdentifier, HeadObjectCommand } from '@aws-sdk/client-s3';
|
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectsCommand, ObjectIdentifier, HeadObjectCommand } from '@aws-sdk/client-s3';
|
||||||
import { CustomError, ErrorCode } from '../../../utils/errors';
|
import { CustomError, CustomErrorCode } from '../../../utils/errors';
|
||||||
import { StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
import { StorageDriverConfig, StorageDriverType } from '../../../utils/types';
|
||||||
import StorageDriverBase from './StorageDriverBase';
|
import StorageDriverBase from './StorageDriverBase';
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ export default class StorageDriverS3 extends StorageDriverBase {
|
|||||||
|
|
||||||
return stream2buffer(response.Body);
|
return stream2buffer(response.Body);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.$metadata?.httpStatusCode === 404) throw new CustomError(`No such item: ${itemId}`, ErrorCode.NotFound);
|
if (error?.$metadata?.httpStatusCode === 404) throw new CustomError(`No such item: ${itemId}`, CustomErrorCode.NotFound);
|
||||||
error.message = `Could not get item "${itemId}": ${error.message}`;
|
error.message = `Could not get item "${itemId}": ${error.message}`;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import { Item } from '../../../services/database/types';
|
import { Item } from '../../../services/database/types';
|
||||||
import { ErrorCode } from '../../../utils/errors';
|
import { CustomErrorCode } from '../../../utils/errors';
|
||||||
import { createUserAndSession, db, makeNoteSerializedBody, models } from '../../../utils/testing/testUtils';
|
import { createUserAndSession, db, makeNoteSerializedBody, models } from '../../../utils/testing/testUtils';
|
||||||
import { Config, StorageDriverConfig, StorageDriverMode } from '../../../utils/types';
|
import { Config, StorageDriverConfig, StorageDriverMode } from '../../../utils/types';
|
||||||
import newModelFactory from '../../factory';
|
import newModelFactory from '../../factory';
|
||||||
@ -291,6 +291,6 @@ export function shouldThrowNotFoundIfNotExist(driverConfig: StorageDriverConfig)
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
expect(error.code).toBe(ErrorCode.NotFound);
|
expect(error.code).toBe(CustomErrorCode.NotFound);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
|
export enum ErrorCode {
|
||||||
|
ResyncRequired = 'resyncRequired',
|
||||||
|
NoPathForItem = 'noPathForItem',
|
||||||
|
HasExclusiveSyncLock = 'hasExclusiveLock',
|
||||||
|
HasSyncLock = 'hasSyncLock',
|
||||||
|
NoSub = 'no_sub',
|
||||||
|
NoStripeSub = 'no_stripe_sub',
|
||||||
|
InvalidOrigin = 'invalidOrigin',
|
||||||
|
}
|
||||||
|
|
||||||
export interface ErrorOptions {
|
export interface ErrorOptions {
|
||||||
details?: any;
|
details?: any;
|
||||||
code?: string;
|
code?: ErrorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For explanation of the setPrototypeOf call, see:
|
// For explanation of the setPrototypeOf call, see:
|
||||||
@ -9,10 +19,10 @@ export class ApiError extends Error {
|
|||||||
public static httpCode: number = 400;
|
public static httpCode: number = 400;
|
||||||
|
|
||||||
public httpCode: number;
|
public httpCode: number;
|
||||||
public code: string;
|
public code: ErrorCode;
|
||||||
public details: any;
|
public details: any;
|
||||||
|
|
||||||
public constructor(message: string, httpCode: number = null, code: string | ErrorOptions = undefined) {
|
public constructor(message: string, httpCode: number = null, code: ErrorCode | ErrorOptions = undefined) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
||||||
this.httpCode = httpCode === null ? 400 : httpCode;
|
this.httpCode = httpCode === null ? 400 : httpCode;
|
||||||
@ -30,7 +40,7 @@ export class ApiError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorWithCode extends ApiError {
|
export class ErrorWithCode extends ApiError {
|
||||||
public constructor(message: string, code: string) {
|
public constructor(message: string, code: ErrorCode) {
|
||||||
super(message, null, code);
|
super(message, null, code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +57,7 @@ export class ErrorMethodNotAllowed extends ApiError {
|
|||||||
export class ErrorNotFound extends ApiError {
|
export class ErrorNotFound extends ApiError {
|
||||||
public static httpCode: number = 404;
|
public static httpCode: number = 404;
|
||||||
|
|
||||||
public constructor(message: string = 'Not Found', code: string = undefined) {
|
public constructor(message: string = 'Not Found', code: ErrorCode = undefined) {
|
||||||
super(message, ErrorNotFound.httpCode, code);
|
super(message, ErrorNotFound.httpCode, code);
|
||||||
Object.setPrototypeOf(this, ErrorNotFound.prototype);
|
Object.setPrototypeOf(this, ErrorNotFound.prototype);
|
||||||
}
|
}
|
||||||
@ -94,7 +104,7 @@ export class ErrorUnprocessableEntity extends ApiError {
|
|||||||
export class ErrorConflict extends ApiError {
|
export class ErrorConflict extends ApiError {
|
||||||
public static httpCode: number = 409;
|
public static httpCode: number = 409;
|
||||||
|
|
||||||
public constructor(message: string = 'Conflict', code: string = undefined) {
|
public constructor(message: string = 'Conflict', code: ErrorCode = undefined) {
|
||||||
super(message, ErrorConflict.httpCode, code);
|
super(message, ErrorConflict.httpCode, code);
|
||||||
Object.setPrototypeOf(this, ErrorConflict.prototype);
|
Object.setPrototypeOf(this, ErrorConflict.prototype);
|
||||||
}
|
}
|
||||||
@ -104,7 +114,7 @@ export class ErrorResyncRequired extends ApiError {
|
|||||||
public static httpCode: number = 400;
|
public static httpCode: number = 400;
|
||||||
|
|
||||||
public constructor(message: string = 'Delta cursor is invalid and the complete data should be resynced') {
|
public constructor(message: string = 'Delta cursor is invalid and the complete data should be resynced') {
|
||||||
super(message, ErrorResyncRequired.httpCode, 'resyncRequired');
|
super(message, ErrorResyncRequired.httpCode, ErrorCode.ResyncRequired);
|
||||||
Object.setPrototypeOf(this, ErrorResyncRequired.prototype);
|
Object.setPrototypeOf(this, ErrorResyncRequired.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,13 +166,13 @@ export function errorToPlainObject(error: any): PlainObjectError {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ErrorCode {
|
export enum CustomErrorCode {
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CustomError extends Error {
|
export class CustomError extends Error {
|
||||||
public code: ErrorCode;
|
public code: CustomErrorCode;
|
||||||
public constructor(message: string, code: ErrorCode) {
|
public constructor(message: string, code: CustomErrorCode) {
|
||||||
super(message);
|
super(message);
|
||||||
this.code = code;
|
this.code = code;
|
||||||
Object.setPrototypeOf(this, CustomError.prototype);
|
Object.setPrototypeOf(this, CustomError.prototype);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import config, { baseUrl } from '../config';
|
import config, { baseUrl } from '../config';
|
||||||
import { Item, ItemAddressingType, User, Uuid } from '../services/database/types';
|
import { Item, ItemAddressingType, User, Uuid } from '../services/database/types';
|
||||||
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from './errors';
|
import { ErrorBadRequest, ErrorCode, ErrorForbidden, ErrorNotFound } from './errors';
|
||||||
import Router from './Router';
|
import Router from './Router';
|
||||||
import { AppContext, HttpMethod, RouteType } from './types';
|
import { AppContext, HttpMethod, RouteType } from './types';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
@ -206,7 +206,7 @@ export async function execRequest(routes: Routers, ctx: AppContext): Promise<Exe
|
|||||||
if (!match) throw new ErrorNotFound();
|
if (!match) throw new ErrorNotFound();
|
||||||
|
|
||||||
const endPoint = match.route.findEndPoint(ctx.request.method as HttpMethod, match.subPath.schema);
|
const endPoint = match.route.findEndPoint(ctx.request.method as HttpMethod, match.subPath.schema);
|
||||||
if (ctx.URL && !isValidOrigin(ctx.URL.origin, baseUrl(endPoint.type), endPoint.type)) throw new ErrorNotFound(`Invalid origin: ${ctx.URL.origin}`, 'invalidOrigin');
|
if (ctx.URL && !isValidOrigin(ctx.URL.origin, baseUrl(endPoint.type), endPoint.type)) throw new ErrorNotFound(`Invalid origin: ${ctx.URL.origin}`, ErrorCode.InvalidOrigin);
|
||||||
|
|
||||||
const isPublicRoute = match.route.isPublic(match.subPath.schema);
|
const isPublicRoute = match.route.isPublic(match.subPath.schema);
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { Subscription, Uuid } from '../services/database/types';
|
|||||||
import { Models } from '../models/factory';
|
import { Models } from '../models/factory';
|
||||||
import { AccountType } from '../models/UserModel';
|
import { AccountType } from '../models/UserModel';
|
||||||
import { findPrice, PricePeriod } from '@joplin/lib/utils/joplinCloud';
|
import { findPrice, PricePeriod } from '@joplin/lib/utils/joplinCloud';
|
||||||
import { ErrorWithCode } from './errors';
|
import { ErrorWithCode, ErrorCode } from './errors';
|
||||||
const stripeLib = require('stripe');
|
const stripeLib = require('stripe');
|
||||||
|
|
||||||
export interface SubscriptionInfo {
|
export interface SubscriptionInfo {
|
||||||
@ -33,11 +33,11 @@ export function accountTypeToPriceId(accountType: AccountType): string {
|
|||||||
|
|
||||||
export async function subscriptionInfoByUserId(models: Models, userId: Uuid): Promise<SubscriptionInfo> {
|
export async function subscriptionInfoByUserId(models: Models, userId: Uuid): Promise<SubscriptionInfo> {
|
||||||
const sub = await models.subscription().byUserId(userId);
|
const sub = await models.subscription().byUserId(userId);
|
||||||
if (!sub) throw new ErrorWithCode('Could not retrieve subscription info', 'no_sub');
|
if (!sub) throw new ErrorWithCode('Could not retrieve subscription info', ErrorCode.NoSub);
|
||||||
|
|
||||||
const stripe = initStripe();
|
const stripe = initStripe();
|
||||||
const stripeSub = await stripe.subscriptions.retrieve(sub.stripe_subscription_id);
|
const stripeSub = await stripe.subscriptions.retrieve(sub.stripe_subscription_id);
|
||||||
if (!stripeSub) throw new ErrorWithCode('Could not retrieve Stripe subscription', 'no_stripe_sub');
|
if (!stripeSub) throw new ErrorWithCode('Could not retrieve Stripe subscription', ErrorCode.NoStripeSub);
|
||||||
|
|
||||||
return { sub, stripeSub };
|
return { sub, stripeSub };
|
||||||
}
|
}
|
||||||
|
@ -466,7 +466,7 @@ export async function expectThrow(asyncFn: Function, errorCode: any = undefined)
|
|||||||
return thrownError;
|
return thrownError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function expectHttpError(asyncFn: Function, expectedHttpCode: number): Promise<void> {
|
export async function expectHttpError(asyncFn: Function, expectedHttpCode: number, expectedErrorCode: string = null): Promise<void> {
|
||||||
let thrownError = null;
|
let thrownError = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -479,6 +479,10 @@ export async function expectHttpError(asyncFn: Function, expectedHttpCode: numbe
|
|||||||
expect('not throw').toBe('throw');
|
expect('not throw').toBe('throw');
|
||||||
} else {
|
} else {
|
||||||
expect(thrownError.httpCode).toBe(expectedHttpCode);
|
expect(thrownError.httpCode).toBe(expectedHttpCode);
|
||||||
|
|
||||||
|
if (expectedErrorCode !== null) {
|
||||||
|
expect(thrownError.code).toBe(expectedErrorCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user