diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3f5e208 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +install: npm install +script: npm test diff --git a/README.md b/README.md index a166ca6..f82d392 100644 --- a/README.md +++ b/README.md @@ -100,13 +100,15 @@ To get help with any command run: nypl-data-api help [command] ``` -Note that the lib draws from the following environment variables, which you can place in `.env`: +Note that the cli uses the following environment variables, read by default from `.env`: - NYPL_API_BASE_URL - NYPL_OAUTH_KEY - NYPL_OAUTH_SECRET - NYPL_OAUTH_URL +See `.env.example` for a sample `.env` file. To specify a different `.env`, use the `--envfile` param (e.g. `--envfile config/qa.env`) + ### Schema post Run the following to upload the content of the given jsonfile to `schemas/[name]` diff --git a/bin/nypl-data-api.js b/bin/nypl-data-api.js index 901b470..44d6d31 100755 --- a/bin/nypl-data-api.js +++ b/bin/nypl-data-api.js @@ -6,9 +6,14 @@ const jsdiff = require('diff') const avsc = require('avsc') require('colors') -const argv = require('minimist')(process.argv.slice(2)) +const argv = require('minimist')(process.argv.slice(2), { + default: { + envfile: '.env' + } +}) + const DataApiClient = require('../') -require('dotenv').config() +require('dotenv').config({ path: argv.envfile }) const log_level = argv.log_level || 'error' @@ -28,6 +33,20 @@ var doPost = function (path, content) { }) } +var doPatch = function (path, content) { + if ((typeof content) !== 'object') { + content = JSON.parse(content) + } + + client.patch(path, content) + .then((resp) => { + console.log(`Done PATCHing ${path}`) + console.log(`Response: ${JSON.stringify(resp, null, 2)}`) + }).catch((e) => { + console.error('Error: ', e) + }) +} + var doGet = function (path) { if (!path) throw new Error('Path required') @@ -62,25 +81,27 @@ var showDiff = function (one, two) { console.log() } -function promptToPost (path, content) { - if (!content) throw new Error('Posting requires content to post') +function promptTo (method, path, content, cb) { + if (argv.content) { + content = fs.readFileSync(argv.content, 'utf8') + } + if (!content) throw new Error(`${method}ing requires content to ${method}`) try { - // Assume all posted bodies are json - console.log('content: ', content) + // Assume all posted/patched bodies are json content = JSON.parse(content) } catch (e) { - throw new Error('Content to POST does not appear to be JSON. Only JSON supported currently.') + throw new Error(`Content to ${method} does not appear to be JSON. Only JSON supported currently.`) } - console.log('POSTING') + console.log(`${method}ing`) console.log(` To endpoint: ${process.env.NYPL_API_BASE_URL}${path}:`) console.log(` Using credentials: ${process.env.NYPL_OAUTH_KEY}@${process.env.NYPL_OAUTH_URL}`) console.log(`${JSON.stringify(content, null, 2)}`) console.log('Proceed?') prompt.start() prompt.get('y/n', (e, result) => { - if (result['y/n'] === 'y') doPost(path, content) + if (result['y/n'] === 'y') cb(path, content) else console.log('Aborting.') }) } @@ -97,10 +118,11 @@ function schemaPost () { if (previous && next) { showDiff(previous, next) } - console.log('Really upload?') + const path = `schemas/${name}` + console.log(`Really upload to ${process.env.NYPL_API_BASE_URL}${path} ?`) prompt.start() prompt.get('y/n', (e, result) => { - if (result['y/n'] === 'y') doPost(`schemas/${name}`, next) + if (result['y/n'] === 'y') doPost(path, next) else console.log('Aborting.') }) } @@ -140,7 +162,13 @@ const commandHash = { description: 'Post JSON to an arbitrary endpoint. Will prepare request and confirm before proceeding.', usage: 'nypl-data-api post [path] [inlinejson]', examples: [ 'nypl-data-api post recap/checkin-requests \'{ "foo": "bar" }\'' ], - exec: (argv.y ? doPost : promptToPost) + exec: (argv.y ? doPost : (path, content) => promptTo('POST', path, content, doPost)) + }, + patch: { + description: 'Patch JSON to an arbitrary endpoint. Will prepare request and confirm before proceeding.', + usage: 'nypl-data-api patch [path] [inlinejson]', + examples: [ 'nypl-data-api patch hold-requests \'{ "success": false }\'' ], + exec: (argv.y ? doPatch : (path, content) => promptTo('PATCH', path, content, doPatch)) }, get: { description: 'Get arbitrary endpoint.', diff --git a/lib/client.js b/lib/client.js index 52f27c8..134c2ba 100644 --- a/lib/client.js +++ b/lib/client.js @@ -89,6 +89,24 @@ class Client { return this._doHttpMethod('POST', path, options) } + /** + * PATCH a resource at an api endpoint + * + * @param {string} path - The path to fetch (e.g. 'current-schema/Item') + * @param {Object} body - The partial object to pass with the request + * @param {RequestOptions} options - A hash of options. + * + * @return {Promise} A promise that resolves the result data + */ + patch (path, body, options = {}) { + options = Object.assign({ + body + }, options) + + log.debug(`patch("${path}", ${JSON.stringify(body)} (${typeof body}), ${JSON.stringify(options)})`) + return this._doHttpMethod('PATCH', path, options) + } + /** * DELETE an object from an api endpoint * diff --git a/package-lock.json b/package-lock.json index 8c529a1..f3aa7a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@nypl/nypl-data-api-client", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/test/data/hold-requests%2F1234-patch.json b/test/data/hold-requests%2F1234-patch.json new file mode 100644 index 0000000..43c8934 --- /dev/null +++ b/test/data/hold-requests%2F1234-patch.json @@ -0,0 +1,24 @@ +{ + "data": { + "id": 1234, + "jobId": "1106896193cb7216d96", + "createdDate": "2021-11-16T10:17:06-05:00", + "updatedDate": "2021-11-16T10:17:12-05:00", + "success": false, + "processed": true, + "patron": "987654321", + "nyplSource": "sierra-nypl", + "requestType": "hold", + "recordType": "i", + "record": "15371503", + "pickupLocation": "", + "deliveryLocation": "NH", + "neededBy": "2022-11-16T00:00:00-05:00", + "numberOfCopies": 1, + "docDeliveryData": null, + "error": null + }, + "count": 1, + "statusCode": 200, + "debugInfo": [] +} diff --git a/test/patch-test.js b/test/patch-test.js new file mode 100644 index 0000000..8d329a8 --- /dev/null +++ b/test/patch-test.js @@ -0,0 +1,54 @@ +let client = null + +describe('Client PATCH method', function () { + beforeEach(() => { + client = require('./make-test-client')() + }) + + afterEach(() => { + client = null + }) + + // This is a common patch that the + // [HoldRequestResultConsumer makes](https://github.com/NYPL/hold-request-result-consumer/blob/e7bd5b04afe25cf65ed2a5ce2de0576973e592cb/src/OAuthClient/HoldRequestClient.php#L123) + // makes when it detects a hold-request has been fulfilled + var holdRequestPatch = { + success: true, + processed: true + } + + describe('when config.json=true (default)', function () { + it('should accept a new schema object via PATCH and return an object', function () { + return client.patch('hold-requests/1234', holdRequestPatch) + .then((resp) => { + expect(resp).to.be.a('object') + }) + }) + + it('should fail if supplied body is plaintext', function () { + let call = client.patch('hold-requests/1234', JSON.stringify(holdRequestPatch)) + return expect(call).to.be.rejected + }) + + // A null/empty body should be accepted as valid if options.json===true + it('should succeed if supplied body is empty', function () { + let call = client.patch('hold-requests/1234') + return expect(call).to.be.fulfilled + }) + }) + + describe('when config.json=false', function () { + it('should accept a plaintext body and return plain text', function () { + let call = client.patch('hold-requests/1234', JSON.stringify(holdRequestPatch), { json: false }) + return Promise.all([ + expect(call).to.be.fulfilled, + expect(call).to.eventually.be.a('string') + ]) + }) + + it('should fail if supplied body is not plaintext', function () { + let call = client.patch('hold-requests/1234', holdRequestPatch, { json: false }) + return expect(call).to.be.rejected + }) + }) +})