Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
},
"ignorePatterns": ["__tests__/**/*"],
"rules": {
"valid-jsdoc": ["error", { "requireReturn": false }],
"valid-jsdoc": 0,
"import/extensions": 0,
"import/no-unresolved": 0,
"camelcase": 0,
"consistent-return": 0,
"@typescript-eslint/no-plusplus": 0,
"prettier/prettier": 2,
Expand All @@ -36,6 +39,8 @@
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-floating-promises": 2,
"max-classes-per-file": 0
"max-classes-per-file": 0,
"no-shadow": 0,
"@typescript-eslint/no-shadow": 2
}
}
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ jobs:
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Publish to NPM
run: |
npm config list
Expand Down
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ coverage.lcov
coverage/
.npmrc
build
/lib
/statistics
/helpers.js
/helpers.d.ts
/helpers.js.map
/models.js
/models.d.ts
/models.js.map
/constants.js
/constants.d.ts
/constants.js.map
2 changes: 1 addition & 1 deletion __tests__/client-id.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { randomUUID } = require('crypto');

const testHomeDir = path.join(__dirname, '__tmp__', 'rp-home');
process.env.RP_CLIENT_JS_HOME = testHomeDir;
const { getClientId } = require('../statistics/client-id');
const { getClientId } = require('../src/statistics/client-id');

const uuidv4Validation = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
const clientIdFile = path.join(testHomeDir, '.rp', 'rp.properties');
Expand Down
4 changes: 2 additions & 2 deletions __tests__/config.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { getClientConfig, getRequiredOption, getApiKey } = require('../lib/commons/config');
const { getClientConfig, getRequiredOption, getApiKey } = require('../src/lib/commons/config');
const {
ReportPortalRequiredOptionError,
ReportPortalValidationError,
} = require('../lib/commons/errors');
} = require('../src/lib/commons/errors');

describe('Config commons test suite', () => {
describe('getRequiredOption', () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/helpers.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const os = require('os');
const fs = require('fs');
const glob = require('glob');
const helpers = require('../lib/helpers');
const helpers = require('../src/lib/helpers');
const pjson = require('../package.json');

describe('Helpers', () => {
Expand Down
86 changes: 85 additions & 1 deletion __tests__/oauth.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const OAuthInterceptor = require('../lib/oauth');
const OAuthInterceptor = require('../src/lib/oauth');

jest.mock('axios', () => ({
post: jest.fn(),
isAxiosError: jest.fn((error) => !!error && typeof error === 'object' && error.isAxiosError === true),
}));

describe('OAuthInterceptor', () => {
Expand Down Expand Up @@ -134,6 +135,7 @@ describe('OAuthInterceptor', () => {
const oauthInterceptor = new OAuthInterceptor(baseConfig);
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
axios.post.mockRejectedValue({
isAxiosError: true,
response: {
status: 400,
data: { error: 'invalid_grant' },
Expand All @@ -150,6 +152,54 @@ describe('OAuthInterceptor', () => {
consoleSpy.mockRestore();
});

it('throws a descriptive error when the token response has no access token', async () => {
const oauthInterceptor = new OAuthInterceptor(baseConfig);
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
// Response resolves successfully but is missing the access_token field.
axios.post.mockResolvedValue({ data: { expires_in: 120 } });

await expect(oauthInterceptor.getAccessToken()).rejects.toThrow(
'OAuth token request failed: No access token received from OAuth server',
);
expect(consoleSpy).toHaveBeenCalledWith(
'[OAuth] OAuth token request failed: No access token received from OAuth server',
);

consoleSpy.mockRestore();
});

it('formats non-Axios errors using their message', async () => {
const oauthInterceptor = new OAuthInterceptor(baseConfig);
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
// A plain Error (not an AxiosError, so no response payload to include).
axios.post.mockRejectedValue(new Error('network is unreachable'));

await expect(oauthInterceptor.getAccessToken()).rejects.toThrow(
'OAuth token request failed: network is unreachable',
);

consoleSpy.mockRestore();
});

it('propagates request errors through the attached rejection handler', async () => {
const oauthInterceptor = new OAuthInterceptor(baseConfig);
let rejectionHandler;
const axiosInstance = {
interceptors: {
request: {
use: jest.fn((fulfilled, rejected) => {
rejectionHandler = rejected;
}),
},
},
};

oauthInterceptor.attach(axiosInstance);
const error = new Error('request setup failed');

await expect(rejectionHandler(error)).rejects.toBe(error);
});

it('logs debug messages only when debug mode is enabled', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const oauthInterceptor = new OAuthInterceptor({
Expand Down Expand Up @@ -231,6 +281,7 @@ describe('OAuthInterceptor', () => {
// First call (refresh token) fails
axios.post
.mockRejectedValueOnce({
isAxiosError: true,
response: {
status: 400,
data: { error: 'invalid_grant', error_description: 'refresh token expired' },
Expand Down Expand Up @@ -286,12 +337,14 @@ describe('OAuthInterceptor', () => {
// Both calls fail
axios.post
.mockRejectedValueOnce({
isAxiosError: true,
response: {
status: 400,
data: { error: 'invalid_grant' },
},
})
.mockRejectedValueOnce({
isAxiosError: true,
response: {
status: 401,
data: { error: 'invalid_credentials' },
Expand Down Expand Up @@ -347,6 +400,37 @@ describe('OAuthInterceptor', () => {
nowSpy.mockRestore();
});

it('logs the proxied token request when debug and proxy are both enabled', async () => {
const baseTime = 1700000750000;
const nowSpy = jest.spyOn(Date, 'now').mockImplementation(() => baseTime);
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const oauthInterceptor = new OAuthInterceptor({
...baseConfig,
restClientConfig: {
debug: true,
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
},
},
});
axios.post.mockResolvedValue({
data: { access_token: 'token-debug-proxy', expires_in: 120 },
});

const token = await oauthInterceptor.getAccessToken();

expect(token).toBe('token-debug-proxy');
expect(consoleSpy).toHaveBeenCalledWith(
`[OAuth] Making token request to ${baseConfig.tokenEndpoint} with proxy agent`,
'',
);

consoleSpy.mockRestore();
nowSpy.mockRestore();
});

it('bypasses proxy for token endpoint when in noProxy list', async () => {
const baseTime = 1700000800000;
const nowSpy = jest.spyOn(Date, 'now').mockImplementation(() => baseTime);
Expand Down
2 changes: 1 addition & 1 deletion __tests__/proxyHelper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const {
getProxyConfig,
createProxyAgents,
getProxyAgentForUrl,
} = require('../lib/proxyHelper');
} = require('../src/lib/proxyHelper');

describe('proxyHelper', () => {
const originalEnv = process.env;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/publicReportingAPI.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const PublicReportingAPI = require('../lib/publicReportingAPI');
const { EVENTS } = require('../lib/constants/events');
const PublicReportingAPI = require('../src/lib/publicReportingAPI');
const { EVENTS } = require('../src/lib/constants/events');

describe('PublicReportingAPI', () => {
it('setDescription should trigger process.emit with correct parameters', () => {
Expand Down
6 changes: 3 additions & 3 deletions __tests__/report-portal-client.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const process = require('process');
const RPClient = require('../lib/report-portal-client');
const helpers = require('../lib/helpers');
const { OUTPUT_TYPES } = require('../lib/constants/outputs');
const RPClient = require('../src/lib/report-portal-client');
const helpers = require('../src/lib/helpers');
const { OUTPUT_TYPES } = require('../src/lib/constants/outputs');

describe('ReportPortal javascript client', () => {
afterEach(() => {
Expand Down
22 changes: 20 additions & 2 deletions __tests__/rest.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const nock = require('nock');
const isEqual = require('lodash/isEqual');
const http = require('http');
const RestClient = require('../lib/rest');
const logger = require('../lib/logger');
const RestClient = require('../src/lib/rest');
const OAuthInterceptor = require('../src/lib/oauth');
const logger = require('../src/lib/logger');

describe('RestClient', () => {
const options = {
Expand Down Expand Up @@ -60,6 +61,23 @@ describe('RestClient', () => {

expect(spyLogger).toHaveBeenCalledWith(client.axiosInstance);
});

it('attaches an OAuth interceptor to the axios instance when oauthConfig is provided', () => {
const attachSpy = jest.spyOn(OAuthInterceptor.prototype, 'attach');
const client = new RestClient({
...options,
oauthConfig: {
tokenEndpoint: 'https://auth.example.com/oauth/token',
username: 'user',
password: 'password',
clientId: 'client-id',
},
});

expect(attachSpy).toHaveBeenCalledWith(client.axiosInstance);

attachSpy.mockRestore();
});
});

describe('retry configuration', () => {
Expand Down
4 changes: 2 additions & 2 deletions __tests__/statistics.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const axios = require('axios');
const Statistics = require('../statistics/statistics');
const { MEASUREMENT_ID, API_KEY } = require('../statistics/constants');
const Statistics = require('../src/statistics/statistics');
const { MEASUREMENT_ID, API_KEY } = require('../src/statistics/constants');

const uuidv4Validation = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;

Expand Down
Loading