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] 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.'); }); });