From 4e05add9f48f846ae27ba02ff628f6969861957f Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jun 2016 16:46:00 +0200 Subject: [PATCH] Switch .post() and .put() to .create() and .update() This one has been a long time coming: POSTing a post to the posts endpoint is a linguistic nightmare, and HTTP verbs are part of the API interface that is best kept away from the users. This alters all of the existing GET, POST, PUT, DELETE and HEAD methods to be (semi)private and prefixed by `_http`, e.g. `_httpDelete`: The public interfaces for these (`.get`, `.create`, `.update`, `.delete` and `.headers`). `.post` and `.put` are maintained for the time being for back-compat but will be removed in 1.0. --- README.md | 21 +++-- lib/constructors/wp-request.js | 108 +++++++++++++++++++--- tests/integration/posts.js | 20 ++-- tests/unit/lib/constructors/wp-request.js | 52 +++++++++++ 4 files changed, 169 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 93203720..ae4bf2be 100644 --- a/README.md +++ b/README.md @@ -125,9 +125,11 @@ site.namespace( 'myplugin/v1' ).authors()... To create a slimmed JSON file dedicated to this particular purpose, see the Node script [lib/data/generate-endpoint-request.js](lib/data/generate-endpoint-request.js), which will let you download and save an endpoint response to your local project. +In addition to retrieving the specified resource with `.get()`, you can also `.create()`, `.update()` and `.delete()` resources: + ### Creating Posts -To create posts, use the `.post()` method on a query to POST a data object to the server (POST is the HTTP "verb" equivalent for "create"): +To create posts, use the `.create()` method on a query to POST (the HTTP verb for "create") a data object to the server: ```js // You must authenticate to be able to POST (create) a post @@ -137,7 +139,7 @@ var wp = new WP({ username: 'someusername', password: 'password' }); -wp.posts().post({ +wp.posts().create({ // "title" and "content" are the only required properties title: 'Your Post Title', content: 'Your post content', @@ -155,7 +157,7 @@ This will work in the same manner for resources other than `post`: you can see t ### Updating Posts -To create posts, use the `.put()` method on a single-item query to PUT a data object to the server (PUT is the HTTP "verb" equivalent for "update"): +To create posts, use the `.update()` method on a single-item query to PUT (the HTTP verb for "update") a data object to the server: ```js // You must authenticate to be able to PUT (update) a post @@ -166,7 +168,7 @@ var wp = new WP({ password: 'password' }); // .id() must be used to specify the post we are updating -wp.posts().id( 2501 ).post({ +wp.posts().id( 2501 ).update({ // Update the title title: 'A Better Title', // Set the post live (assuming it was "draft" before) @@ -183,13 +185,18 @@ This will work in the same manner for resources other than `post`: you can see t A WP instance object provides the following basic request methods: * `wp.posts()...`: Request items from the `/posts` endpoints -* `wp.taxonomies()...`: Generate a request against the `/taxonomies` endpoints * `wp.pages()...`: Start a request for the `/pages` endpoints -* `wp.users()...`: Get resources within the `/users` endpoints * `wp.types()...`: Get Post Type collections and objects from the `/types` endpoints +* `wp.comments()...`: Start a request for the `/comments` endpoints +* `wp.taxonomies()...`: Generate a request against the `/taxonomies` endpoints +* `wp.tags()...`: Get or create tags with the `/tags` endpoint +* `wp.categories()...`: Get or create categories with the `/categories` endpoint +* `wp.statuses()...`: Get the available statuses within the `/statuses` endpoint +* `wp.statuses()...`: Get resources within the `/statuses` endpoints +* `wp.users()...`: Get resources within the `/users` endpoints * `wp.media()...`: Get Media collections and objects from the `/media` endpoints -All of these methods return a customizable request object. The request object can be further refined with chaining methods, and/or sent to the server via `.get()`, `.post()`, `.put()`, `.delete()`, `.head()`, or `.then()`. (Not all endpoints support all methods; for example, you cannot POST or PUT records on `/types`, as these are defined in WordPress plugin or theme code.) +All of these methods return a customizable request object. The request object can be further refined with chaining methods, and/or sent to the server via `.get()`, `.create()`, `.update()`, `.delete()`, `.headers()`, or `.then()`. (Not all endpoints support all methods; for example, you cannot POST or PUT records on `/types`, as these are defined in WordPress plugin or theme code.) Additional querying methods provided, by endpoint: diff --git a/lib/constructors/wp-request.js b/lib/constructors/wp-request.js index bd5517af..5bda2a85 100644 --- a/lib/constructors/wp-request.js +++ b/lib/constructors/wp-request.js @@ -617,8 +617,8 @@ WPRequest.prototype._renderURI = function() { // Render the path to a string var path = this._renderPath(); - // If the current request supports filters, render them to a query string - var queryStr = this._renderQuery ? this._renderQuery() : ''; + // Render the query string + var queryStr = this._renderQuery(); return this._options.endpoint + path + queryStr; }; @@ -722,16 +722,17 @@ WPRequest.prototype.auth = function( usrOrObj, password ) { return this; }; -// HTTP Methods -// ============ +// HTTP Methods: Private HTTP-verb versions +// ======================================== /** - * @method get + * @method _httpGet * @async + * @private * @param {Function} [callback] A callback to invoke with the results of the GET request * @return {Promise} A promise to the results of the HTTP request */ -WPRequest.prototype.get = function( callback ) { +WPRequest.prototype._httpGet = function( callback ) { this._checkMethodSupport( 'get' ); var url = this._renderURI(); @@ -741,13 +742,15 @@ WPRequest.prototype.get = function( callback ) { }; /** - * @method post + * Invoke an HTTP "POST" request against the provided endpoint + * @method _httpPost * @async + * @private * @param {Object} data The data for the POST request * @param {Function} [callback] A callback to invoke with the results of the POST request * @return {Promise} A promise to the results of the HTTP request */ -WPRequest.prototype.post = function( data, callback ) { +WPRequest.prototype._httpPost = function( data, callback ) { this._checkMethodSupport( 'post' ); var url = this._renderURI(); data = data || {}; @@ -758,13 +761,14 @@ WPRequest.prototype.post = function( data, callback ) { }; /** - * @method put + * @method _httpPut * @async + * @private * @param {Object} data The data for the PUT request * @param {Function} [callback] A callback to invoke with the results of the PUT request * @return {Promise} A promise to the results of the HTTP request */ -WPRequest.prototype.put = function( data, callback ) { +WPRequest.prototype._httpPut = function( data, callback ) { this._checkMethodSupport( 'put' ); var url = this._renderURI(); data = data || {}; @@ -775,13 +779,14 @@ WPRequest.prototype.put = function( data, callback ) { }; /** - * @method delete + * @method _httpDelete * @async + * @private * @param {Object} [data] Data to send along with the DELETE request * @param {Function} [callback] A callback to invoke with the results of the DELETE request * @return {Promise} A promise to the results of the HTTP request */ -WPRequest.prototype.delete = function( data, callback ) { +WPRequest.prototype._httpDelete = function( data, callback ) { if ( ! callback && typeof data === 'function' ) { callback = data; data = null; @@ -794,12 +799,13 @@ WPRequest.prototype.delete = function( data, callback ) { }; /** - * @method head + * @method _httpHead * @async + * @private * @param {Function} [callback] A callback to invoke with the results of the HEAD request * @return {Promise} A promise to the header results of the HTTP request */ -WPRequest.prototype.head = function( callback ) { +WPRequest.prototype._httpHead = function( callback ) { this._checkMethodSupport( 'head' ); var url = this._renderURI(); var request = this._auth( agent.head( url ) ); @@ -807,6 +813,78 @@ WPRequest.prototype.head = function( callback ) { return invokeAndPromisify( request, callback, returnHeaders ); }; +// HTTP Methods: Public Interface +// ============================== + +/** @deprecated Use .create() */ +WPRequest.prototype.post = function( data, callback ) { + return this._httpPost( data, callback ); +}; + +/** @deprecated Use .update() */ +WPRequest.prototype.put = function( data, callback ) { + return this._httpPut( data, callback ); +}; + +/** + * @method get + * @async + * @param {Function} [callback] A callback to invoke with the results of the GET request + * @return {Promise} A promise to the results of the HTTP request + */ +WPRequest.prototype.get = function( callback ) { + return this._httpGet( callback ); +}; + +/** + * Create a HEAD request against a site + * @method headers + * @async + * @param {Function} [callback] A callback to invoke with the results of the HEAD request + * @return {Promise} A promise to the header results of the HTTP request + */ +WPRequest.prototype.headers = function( callback ) { + return this._httpHead( callback ); +}; + +/** + * Invoke an HTTP "POST" request against the provided endpoint + * + * This is the public interface creating for POST requests + * + * @method create + * @async + * @param {Object} data The data for the POST request + * @param {Function} [callback] A callback to invoke with the results of the POST request + * @return {Promise} A promise to the results of the HTTP request + */ +WPRequest.prototype.create = function( data, callback ) { + return this._httpPost( data, callback ); +}; + +/** + * @method _httpPut + * @async + * @private + * @param {Object} data The data for the PUT request + * @param {Function} [callback] A callback to invoke with the results of the PUT request + * @return {Promise} A promise to the results of the HTTP request + */ +WPRequest.prototype.update = function( data, callback ) { + return this._httpPut( data, callback ); +}; + +/** + * @method delete + * @async + * @param {Object} [data] Data to send along with the DELETE request + * @param {Function} [callback] A callback to invoke with the results of the DELETE request + * @return {Promise} A promise to the results of the HTTP request + */ +WPRequest.prototype.delete = function( data, callback ) { + return this._httpDelete( data, callback ); +}; + /** * Calling .then on a query chain will invoke the query as a GET and return a promise * @@ -817,7 +895,7 @@ WPRequest.prototype.head = function( callback ) { * @return {Promise} A promise to the results of the HTTP request */ WPRequest.prototype.then = function( successCallback, failureCallback ) { - return this.get().then( successCallback, failureCallback ); + return this._httpGet().then( successCallback, failureCallback ); }; module.exports = WPRequest; diff --git a/tests/integration/posts.js b/tests/integration/posts.js index bc2bceb9..9fb295e5 100644 --- a/tests/integration/posts.js +++ b/tests/integration/posts.js @@ -114,10 +114,10 @@ describe( 'integration: posts()', function() { return expect( prom ).to.eventually.equal( SUCCESS ); }); - it( 'include the total number of posts', function() { - var prom = wp.posts().get().then(function( posts ) { - expect( posts._paging ).to.have.property( 'total' ); - expect( posts._paging.total ).to.equal( '38' ); + it( 'include the total number of posts: use .headers() for coverage reasons', function() { + var prom = wp.posts().headers().then(function( postHeadersResponse ) { + expect( postHeadersResponse ).to.have.property( 'x-wp-total' ); + expect( postHeadersResponse[ 'x-wp-total' ] ).to.equal( '38' ); return SUCCESS; }); return expect( prom ).to.eventually.equal( SUCCESS ); @@ -301,8 +301,8 @@ describe( 'integration: posts()', function() { return expect( prom ).to.eventually.equal( SUCCESS ); }); - it( 'cannot POST (create) without authentication', function() { - var prom = wp.posts().post({ + it( 'cannot create (POST) without authentication', function() { + var prom = wp.posts().create({ title: 'New Post 2501', content: 'Some Content' }).catch(function( err ) { @@ -314,11 +314,11 @@ describe( 'integration: posts()', function() { return expect( prom ).to.eventually.equal( SUCCESS ); }); - it( 'cannot PUT (update) without authentication', function() { + it( 'cannot update (PUT) without authentication', function() { var id; var prom = wp.posts().perPage( 1 ).get().then(function( posts ) { id = posts[ 0 ].id; - return wp.posts().id( id ).put({ + return wp.posts().id( id ).update({ title: 'New Post 2501', content: 'Some Content' }); @@ -333,7 +333,7 @@ describe( 'integration: posts()', function() { it( 'can create, update & delete a post when authenticated', function() { var id; - var prom = wp.posts().auth( credentials ).post({ + var prom = wp.posts().auth( credentials ).create({ title: 'New Post 2501', content: 'Some Content' }).then(function( createdPost ) { @@ -347,7 +347,7 @@ describe( 'integration: posts()', function() { expect( createdPost ).to.have.property( 'content' ); expect( createdPost.content ).to.have.property( 'raw' ); expect( createdPost.content.raw ).to.equal( 'Some Content' ); - return wp.posts().auth( credentials ).id( id ).put({ + return wp.posts().auth( credentials ).id( id ).update({ title: 'Updated Title', status: 'publish' }); diff --git a/tests/unit/lib/constructors/wp-request.js b/tests/unit/lib/constructors/wp-request.js index 482c4ee9..bf11ac76 100644 --- a/tests/unit/lib/constructors/wp-request.js +++ b/tests/unit/lib/constructors/wp-request.js @@ -322,6 +322,19 @@ describe( 'WPRequest', function() { expect( request._options.auth ).to.be.true; }); + it( 'does not set username/password if they are not provided as string values', function() { + expect( request._options ).not.to.have.property( 'username' ); + expect( request._options ).not.to.have.property( 'password' ); + request.auth({ + username: 123, + password: false + }); + expect( request._options ).not.to.have.property( 'username' ); + expect( request._options ).not.to.have.property( 'password' ); + expect( request._options ).to.have.property( 'auth' ); + expect( request._options.auth ).to.be.true; + }); + }); // auth describe( '._auth', function() { @@ -834,4 +847,43 @@ describe( 'WPRequest', function() { }); // Pagination }); // Request methods + + describe( 'deprecated request methods', function() { + + describe( '.post()', function() { + + it( 'is a function', function() { + expect( request ).to.have.property( 'post' ); + expect( request.post ).to.be.a( 'function' ); + }); + + it( 'proxies to ._httpPost', function() { + sinon.stub( request, '_httpPost' ); + function cb() {} + request.post( 'foo', cb ); + expect( request._httpPost ).to.have.been.calledWith( 'foo', cb ); + request._httpPost.restore(); + }); + + }); + + describe( '.put()', function() { + + it( 'is a function', function() { + expect( request ).to.have.property( 'put' ); + expect( request.put ).to.be.a( 'function' ); + }); + + it( 'proxies to ._httpPut', function() { + sinon.stub( request, '_httpPut' ); + function cb() {} + request.put( 'foo', cb ); + expect( request._httpPut ).to.have.been.calledWith( 'foo', cb ); + request._httpPut.restore(); + }); + + }); + + }); // Deprecated request methods + });