From c76a5d727f593725bbd75268d10b9c6228940d11 Mon Sep 17 00:00:00 2001 From: "Pimm \"de Chinchilla\" Hogeling" Date: Fri, 5 Jul 2024 15:59:41 +0200 Subject: [PATCH 1/2] Add a more helpful error to createMollieClient for missing credentials. Previously, not setting apiKey or accessToken to an appropriate value would result in a generic error. With this change, an accidental empty string gives a distinct error from no value at all. --- src/createMollieClient.ts | 22 +++++++++++++++++++++- tests/unit/createMollieClient.test.ts | 16 +++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/createMollieClient.ts b/src/createMollieClient.ts index 9e83a2ce..89e5775b 100644 --- a/src/createMollieClient.ts +++ b/src/createMollieClient.ts @@ -4,6 +4,7 @@ import caCertificates from './cacert.pem'; import NetworkClient from './communication/NetworkClient'; import TransformingNetworkClient, { Transformers } from './communication/TransformingNetworkClient'; import type Options from './Options'; +import { run } from 'ruply'; // Transformers import { transform as transformPayment } from './data/payments/Payment'; @@ -58,6 +59,25 @@ import SettlementsBinder from './binders/settlements/SettlementsBinder'; import SubscriptionsBinder from './binders/subscriptions/SubscriptionsBinder'; import SubscriptionPaymentsBinder from './binders/subscriptions/payments/SubscriptionPaymentsBinder'; +/** + * Returns an error message (string) similar to `"Parameter "×" is null."` if a property with the passed key exists in + * the passed object, or `null` if said property does not exist. + */ +const describeFalsyOption = run( + new Map([ + [undefined, 'undefined'], + [null, 'null'], + ['', 'an empty string'], + ]) as Map, + descriptions => + function describeFalsyOption>(object: O, key: keyof O & string) { + if (key in object == false) { + return null; + } + return `Parameter "${key}" is ${descriptions.get(object[key]) ?? object[key]}.`; + }, +); + /** * Create Mollie client. * @since 2.0.0 @@ -71,7 +91,7 @@ export default function createMollieClient(options: Options) { } if (!options.apiKey && !options.accessToken) { - throw new TypeError('Missing parameter "apiKey" or "accessToken".'); + throw new TypeError(describeFalsyOption(options, 'apiKey') ?? describeFalsyOption(options, 'accessToken') ?? 'Missing parameter "apiKey" or "accessToken".'); } const networkClient = new NetworkClient({ ...options, libraryVersion, nodeVersion: process.version, caCertificates }); diff --git a/tests/unit/createMollieClient.test.ts b/tests/unit/createMollieClient.test.ts index ee145d1f..6a40a37f 100644 --- a/tests/unit/createMollieClient.test.ts +++ b/tests/unit/createMollieClient.test.ts @@ -1,9 +1,19 @@ import createMollieClient from '../..'; -describe('mollie', () => { - it('should fail when no api key is provided', () => { +describe('createMollieClient', () => { + it('should fail when no credentials are provided', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore - expect(() => createMollieClient(undefined)).toThrowError(TypeError); + expect(() => createMollieClient({})).toThrow('Missing parameter "apiKey" or "accessToken".'); + }); + + it('should throw a descriptive error when a apiKey is set to null', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore + expect(() => createMollieClient({ apiKey: null })).toThrow('Parameter "apiKey" is null.'); + }); + + it('should throw a descriptive error when a apiKey is set to an empty string', () => { + expect(() => createMollieClient({ apiKey: '' })).toThrow('Parameter "apiKey" is an empty string.'); }); }); From 70edc2ef8304128942f3d4ffdb838dd55a2f3228 Mon Sep 17 00:00:00 2001 From: "Pimm \"de Chinchilla\" Hogeling" Date: Fri, 5 Jul 2024 16:36:15 +0200 Subject: [PATCH 2/2] Move credentials check. --- src/Options.ts | 26 ++++++++++++++++++++++++++ src/createMollieClient.ts | 25 ++----------------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/Options.ts b/src/Options.ts index 271b0faf..42156409 100644 --- a/src/Options.ts +++ b/src/Options.ts @@ -31,4 +31,30 @@ type Options = Xor< apiEndpoint?: string; } & Pick; +const falsyDescriptions = new Map([ + [undefined, 'undefined'], + [null, 'null'], + ['', 'an empty string'], +]); + +/** + * Returns an error message (string) similar to `"Parameter "×" is null."` if a property with the passed key exists in + * the passed options object, or `null` if said property does not exist. + */ +function describeFalsyOption(options: Options, key: keyof Options) { + if (key in options == false) { + return null; + } + return `Parameter "${key}" is ${falsyDescriptions.get(options[key]) ?? options[key]}.`; +}; + +/** + * Throws a `TypeError` if the passed options object does not contain an `apiKey` or an `accessToken`. + */ +export function checkCredentials(options: Options) { + if (!options.apiKey && !options.accessToken) { + throw new TypeError(describeFalsyOption(options, 'apiKey') ?? describeFalsyOption(options, 'accessToken') ?? 'Missing parameter "apiKey" or "accessToken".'); + } +} + export default Options; diff --git a/src/createMollieClient.ts b/src/createMollieClient.ts index 89e5775b..60c70485 100644 --- a/src/createMollieClient.ts +++ b/src/createMollieClient.ts @@ -3,8 +3,8 @@ import { version as libraryVersion } from '../package.json'; import caCertificates from './cacert.pem'; import NetworkClient from './communication/NetworkClient'; import TransformingNetworkClient, { Transformers } from './communication/TransformingNetworkClient'; +import { checkCredentials } from './Options'; import type Options from './Options'; -import { run } from 'ruply'; // Transformers import { transform as transformPayment } from './data/payments/Payment'; @@ -59,25 +59,6 @@ import SettlementsBinder from './binders/settlements/SettlementsBinder'; import SubscriptionsBinder from './binders/subscriptions/SubscriptionsBinder'; import SubscriptionPaymentsBinder from './binders/subscriptions/payments/SubscriptionPaymentsBinder'; -/** - * Returns an error message (string) similar to `"Parameter "×" is null."` if a property with the passed key exists in - * the passed object, or `null` if said property does not exist. - */ -const describeFalsyOption = run( - new Map([ - [undefined, 'undefined'], - [null, 'null'], - ['', 'an empty string'], - ]) as Map, - descriptions => - function describeFalsyOption>(object: O, key: keyof O & string) { - if (key in object == false) { - return null; - } - return `Parameter "${key}" is ${descriptions.get(object[key]) ?? object[key]}.`; - }, -); - /** * Create Mollie client. * @since 2.0.0 @@ -90,9 +71,7 @@ export default function createMollieClient(options: Options) { ); } - if (!options.apiKey && !options.accessToken) { - throw new TypeError(describeFalsyOption(options, 'apiKey') ?? describeFalsyOption(options, 'accessToken') ?? 'Missing parameter "apiKey" or "accessToken".'); - } + checkCredentials(options); const networkClient = new NetworkClient({ ...options, libraryVersion, nodeVersion: process.version, caCertificates });