You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Chore: Server: Refactor error codes
This commit is contained in:
		| @@ -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); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user