diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index ecba6be..6649462 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,24 +5,33 @@ name: Node.js CI on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: - runs-on: ubuntu-latest strategy: matrix: - node-version: [10.x, 12.x, 14.x] + include: + - node: 18 + npm: ^9 + - node: 20 + npm: ^10 + - node: 22 + npm: ^10 + - node: 24 + npm: ^11 + - node: latest + npm: latest steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm test + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test diff --git a/package.json b/package.json index 5c87b84..fe5f7b5 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "adm-zip": "^0.5.2", - "axios": "^0.24.0", + "axios": "1.14.0", "chalk": "^4.1.0", "form-data": "^3.0.0", "glob": "^7.1.6", diff --git a/src/common/rollbar-api.js b/src/common/rollbar-api.js index 9be770d..515a875 100644 --- a/src/common/rollbar-api.js +++ b/src/common/rollbar-api.js @@ -1,5 +1,18 @@ 'use strict'; +// Helper to normalize axios error/response for consistent error handling +function normalizeAxiosError(resp) { + if (!resp) return { statusText: 'Axios Error', message: 'Unknown error' }; + if (resp.status === 200) return null; + if (typeof resp.data === 'string') { + return { statusText: resp.statusText || 'Axios Error', message: resp.data }; + } + if (typeof resp.data === 'object' && resp.data !== null) { + return Object.assign({}, resp.data, { statusText: resp.statusText || 'Axios Error' }); + } + return { statusText: resp.statusText || 'Axios Error', message: String(resp.data) }; +} + const axios = require('axios'); const FormData = require('form-data'); @@ -21,44 +34,57 @@ class RollbarAPI { async deploy(request, deployId) { let resp; - if(deployId) { - output.verbose('', 'Update to an existing deploy with deploy_id: ' + deployId); - resp = await this.axios.patch('/deploy/' + deployId, request); - } else { - output.verbose('','deploy_id not present so likely a new deploy'); - resp = await this.axios.post('/deploy', request); - } + try { + if(deployId) { + output.verbose('', 'Update to an existing deploy with deploy_id: ' + deployId); + resp = await this.axios.patch('/deploy/' + deployId, request); + } else { + output.verbose('','deploy_id not present so likely a new deploy'); + resp = await this.axios.post('/deploy', request); + } - // Output deploy-id - if (resp.status === 200) { - output.success('', resp.data.data); + // Output deploy-id + if (resp.status === 200) { + output.success('', resp.data.data); + } + return this.processResponse(resp); + } catch (error) { + output.verbose('', 'axios threw error:', error); + return this.processResponse(error.response || { data: error.message, status: error.status || 500, statusText: error.statusText || 'Axios Error' }); } - return this.processResponse(resp); } async sigendURLsourcemaps(request) { - - const resp = await this.axios.post( - '/signed_url/sourcemap_bundle', { version: request.version , prefix_url: request.baseUrl} - ); - return this.processSignedURLResponse(resp); + try { + const resp = await this.axios.post( + '/signed_url/sourcemap_bundle', { version: request.version , prefix_url: request.baseUrl} + ); + return this.processSignedURLResponse(resp); + } catch (error) { + output.verbose('', 'axios threw error:', error); + return this.processSignedURLResponse(error.response || { data: error.message, status: error.status || 500, statusText: error.statusText || 'Axios Error' }); + } } async sourcemaps(request) { output.verbose('', 'minified_url: ' + request.minified_url); const form = this.convertRequestToForm(request); - const resp = await this.axios.post( - '/sourcemap', - form.getBuffer(), // use buffer to prevent unwanted string escaping. - { headers: { - // axios needs some help with headers for form data. - 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`, - 'Content-Length': form.getLengthSync() - }} - ); - - return this.processResponse(resp); + try { + const resp = await this.axios.post( + '/sourcemap', + form.getBuffer(), // use buffer to prevent unwanted string escaping. + { headers: { + // axios needs some help with headers for form data. + 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`, + 'Content-Length': form.getLengthSync() + }} + ); + return this.processResponse(resp); + } catch (error) { + output.verbose('', 'axios threw error:', error); + return this.processResponse(error.response || { data: error.message, status: error.status || 500, statusText: error.statusText || 'Axios Error' }); + } } convertRequestToForm(request) { @@ -80,15 +106,15 @@ class RollbarAPI { processSignedURLResponse(resp) { output.verbose('', 'response:', resp.data, resp.status, resp.statusText); - return resp.data; + if (resp.status === 200) { + return resp.data; + } + return normalizeAxiosError(resp); } processResponse(resp) { output.verbose('', 'response:', resp.data, resp.status, resp.statusText); - if (resp.status === 200) { - return null; - } - return resp.data; + return normalizeAxiosError(resp); } } diff --git a/test/common/rollbar-api.test.js b/test/common/rollbar-api.test.js index 3ef894b..08e6593 100644 --- a/test/common/rollbar-api.test.js +++ b/test/common/rollbar-api.test.js @@ -285,3 +285,40 @@ describe('.deploy() with deployId', function() { }); }); +describe('RollbarAPI error handling (catch coverage)', function() { + beforeEach(function() { + const accessToken = 'abcd'; + this.rollbarAPI = new RollbarAPI(accessToken); + global.output = new Output({verbose: false}); + }); + + afterEach(function() { + global.output = null; + }); + + it('should handle axios throwing in deploy()', async function() { + // Force axios.patch to reject + const stub = sinon.stub(this.rollbarAPI.axios, 'patch').rejects(new Error('Network error')); + const response = await this.rollbarAPI.deploy({}, '123'); + expect(response).to.be.a('object'); + expect(response.statusText).to.equal('Axios Error'); + stub.restore(); + }); + + it('should handle axios throwing in sigendURLsourcemaps()', async function() { + const stub = sinon.stub(this.rollbarAPI.axios, 'post').rejects(new Error('Network error')); + const response = await this.rollbarAPI.sigendURLsourcemaps({ version: '1', baseUrl: 'https://example.com/' }); + expect(response).to.be.a('object'); + expect(response.statusText).to.equal('Axios Error'); + stub.restore(); + }); + + it('should handle axios throwing in sourcemaps()', async function() { + const stub = sinon.stub(this.rollbarAPI.axios, 'post').rejects(new Error('Network error')); + const response = await this.rollbarAPI.sourcemaps({ version: '1', minified_url: 'https://example.com/', source_map: 'abc' }); + expect(response).to.be.a('object'); + expect(response.statusText).to.equal('Axios Error'); + stub.restore(); + }); +}); + diff --git a/test/sourcemaps/scanner.test.js b/test/sourcemaps/scanner.test.js index 3eaef9a..322e46e 100644 --- a/test/sourcemaps/scanner.test.js +++ b/test/sourcemaps/scanner.test.js @@ -139,7 +139,7 @@ describe('.scan()', function() { await scanner.scan(); const files = scanner.mappedFiles(); - expect(files[0].errors[0].error).to.have.string('Error parsing map file: Unexpected token $ in JSON at position 24'); + expect(files[0].errors[0].error).to.have.string('Error parsing map file:'); expect(files[1].errors[0].error).to.have.string('Error parsing map file: "sources" is a required argument'); }); });