From c56b287476f7719c4a024c9f0f8292ce6710625a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 03:45:50 +0000 Subject: [PATCH 1/2] Bump axios from 1.12.0 to 1.13.5 Bumps [axios](https://github.com/axios/axios) from 1.12.0 to 1.13.5. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.12.0...v1.13.5) --- updated-dependencies: - dependency-name: axios dependency-version: 1.13.5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 68d36bc..38f7bd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3559,13 +3559,13 @@ } }, "node_modules/axios": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz", - "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -5391,15 +5391,16 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5419,9 +5420,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", From ed13b6932370f9d2b96eda048705e3e454dec95c Mon Sep 17 00:00:00 2001 From: I339886 Date: Wed, 25 Mar 2026 10:09:26 +0200 Subject: [PATCH 2/2] fix --- CLAUDE.md | 66 +++++++++++++++++++++++++++++ __tests__/utils/axios-utils.test.ts | 29 +++++++------ 2 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a4cf009 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,66 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Run a single test file +npx jest __tests__/client.test.ts + +# Lint +npm run lint + +# Lint with auto-fix +npm run lint:fix + +# Build (compile TypeScript to dist/) +npx tsc + +# Full pre-publish check (test + lint + build) +npm run prepublishOnly +``` + +## Architecture + +This is a Promise-based Node.js client library for the SAP Alert Notification service for SAP BTP. It is published as `@sap_oss/alert-notification-client`. + +**Entry point:** `src/index.ts` — barrel export of all public APIs. Build output goes to `dist/`. + +### Public API surface (`src/client.ts`) + +`AlertNotificationClient` is the main class consumers instantiate. It delegates to two sub-clients: +- `ConfigurationApiClient` (`src/configuration-api/`) — CRUD for Actions, Conditions, Subscriptions +- `EventsApiClient` (`src/producer-api/`) — sending events and pulling stored/undelivered events + +### Authentication (`src/authentication.ts`) + +Three strategies implementing a common `Authentication` interface: +- `BasicAuthentication` — static Base64 header +- `OAuthAuthentication` — fetches and caches tokens, handles expiry +- `CertificateAuthentication` — mTLS client certificates (JKS/P12/PFX/PEM via `src/utils/key-store.ts`) + +### HTTP layer (`src/utils/axios-utils.ts`) + +Axios interceptors are applied in order: +1. Authorization header injection (request) +2. Retry logic (request/response) +3. Response data extraction (response) + +### Region mapping (`src/utils/region.ts`) + +Maps 26+ SAP BTP region codes (e.g. `EU10`, `US20`) to their CloudFoundry, mTLS, and Mesh endpoint URLs. `RegionUtils` is the public export. + +### Destination service integration (`src/utils/destination-configuration.ts`) + +Optional: resolves connection details from the SAP Destination Service at runtime instead of hard-coding credentials. + +## Testing + +Jest with `ts-jest`. Tests live in `__tests__/`, mirroring the `src/` structure. Coverage threshold is 80% across branches, functions, lines, and statements. diff --git a/__tests__/utils/axios-utils.test.ts b/__tests__/utils/axios-utils.test.ts index c0b8f3c..68431fd 100644 --- a/__tests__/utils/axios-utils.test.ts +++ b/__tests__/utils/axios-utils.test.ts @@ -1,4 +1,4 @@ -import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; +import axios, { AxiosInstance, AxiosHeaders, InternalAxiosRequestConfig } from 'axios'; import { configureDefaultRetryInterceptor, @@ -15,11 +15,11 @@ const mockedAuthentication = { }; let classUnderTest: AxiosInstance; -let axiosRequestConfig: AxiosRequestConfig; +let axiosRequestConfig: InternalAxiosRequestConfig; beforeEach(() => { classUnderTest = axios.create(); - axiosRequestConfig = {}; + axiosRequestConfig = { headers: new AxiosHeaders() }; }); describe('when setupAuthorizationHeaderOnRequestInterceptor is called', () => { @@ -35,7 +35,7 @@ describe('when setupAuthorizationHeaderOnRequestInterceptor is called', () => { setupAuthorizationHeaderOnRequestInterceptor(classUnderTest, Promise.resolve(keyStore)); const fullfilledHandler = classUnderTest.interceptors.request['handlers'][0].fulfilled; - return fullfilledHandler(axiosRequestConfig).then((adjustedConfig) => { + return Promise.resolve(fullfilledHandler(axiosRequestConfig)).then((adjustedConfig: any) => { expect(adjustedConfig.httpsAgent.options.pfx).toBeDefined(); expect(adjustedConfig.httpsAgent.options.passphrase).toBe('passphrase'); expect(adjustedConfig.httpsAgent.options.cert).toBeUndefined(); @@ -48,7 +48,7 @@ describe('when setupAuthorizationHeaderOnRequestInterceptor is called', () => { setupAuthorizationHeaderOnRequestInterceptor(classUnderTest, Promise.resolve(keyStore)); const fullfilledHandler = classUnderTest.interceptors.request['handlers'][0].fulfilled; - return fullfilledHandler(axiosRequestConfig).then((adjustedConfig) => { + return Promise.resolve(fullfilledHandler(axiosRequestConfig)).then((adjustedConfig: any) => { expect(adjustedConfig.httpsAgent.options.pfx).toBeUndefined(); expect(adjustedConfig.httpsAgent.options.passphrase).toBe('passphrase'); expect(adjustedConfig.httpsAgent.options.cert).toBeDefined(); @@ -60,10 +60,10 @@ describe('when setupAuthorizationHeaderOnRequestInterceptor is called', () => { describe('sets up a request handler which', () => { beforeEach(() => { axiosRequestConfig = { - headers: { + headers: new AxiosHeaders({ 'content-length': 'application-json', 'test-header': 'test-value' - } + }) }; setupAuthorizationHeaderOnRequestInterceptor( @@ -80,7 +80,7 @@ describe('when setupAuthorizationHeaderOnRequestInterceptor is called', () => { axiosRequestConfig = { ...axiosRequestConfig, auth }; const fullfilledHandler = classUnderTest.interceptors.request['handlers'][0].fulfilled; - return fullfilledHandler(axiosRequestConfig).then((adjustedConfig) => + return Promise.resolve(fullfilledHandler(axiosRequestConfig)).then((adjustedConfig: any) => expect(adjustedConfig.auth).not.toBeDefined() ); }); @@ -88,7 +88,7 @@ describe('when setupAuthorizationHeaderOnRequestInterceptor is called', () => { test('populates headers field with Authorization header', () => { const fullfilledHandler = classUnderTest.interceptors.request['handlers'][0].fulfilled; - return fullfilledHandler(axiosRequestConfig).then((adjustedConfig) => + return Promise.resolve(fullfilledHandler(axiosRequestConfig)).then((adjustedConfig: any) => expect(adjustedConfig.headers.Authorization).toEqual(authorizationValue) ); }); @@ -106,12 +106,15 @@ describe('when extractDataOnResponseInterceptor is called', () => { test: 'test-value' }, status: 200, + statusText: 'OK', + headers: new AxiosHeaders(), + config: axiosRequestConfig, otherObject: { other: 'other-data' } }; - return fullfilledHandler(responseObject).then((actualdata) => + return Promise.resolve(fullfilledHandler(responseObject)).then((actualdata) => expect(actualdata).toEqual(responseObject.data) ); }); @@ -134,7 +137,7 @@ describe('when configureDefaultRetryInterceptor is called', () => { describe('sets up a request handler which', () => { test('sets default retry configuration if it is not provided', () => { const fullfilledHandler = classUnderTest.interceptors.request['handlers'][0].fulfilled; - const adjustedRequestConfig = fullfilledHandler(axiosRequestConfig); + const adjustedRequestConfig = fullfilledHandler(axiosRequestConfig) as any; expect(adjustedRequestConfig.retryConfig).toStrictEqual({ currentAttempt: 0, @@ -145,10 +148,10 @@ describe('when configureDefaultRetryInterceptor is called', () => { }); test('sets the provided retry configuration', () => { - axiosRequestConfig = { ...axiosRequestConfig, ...{ retryConfig } }; + axiosRequestConfig = { ...axiosRequestConfig, ...{ retryConfig } } as any; const fullfilledHandler = classUnderTest.interceptors.request['handlers'][0].fulfilled; - const adjustedRequestConfig = fullfilledHandler(axiosRequestConfig); + const adjustedRequestConfig = fullfilledHandler(axiosRequestConfig) as any; expect(adjustedRequestConfig.retryConfig).toStrictEqual({ ...retryConfig,