├── .gitignore ├── README.md ├── index.js ├── lib ├── create.js ├── delete.js ├── isURL.js ├── read.js ├── update.js └── validAddress.js ├── package.json └── test ├── create.spec.js ├── delete.spec.js ├── read.spec.js ├── test.js └── update.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # sheetsu-node 3 | 5 | 6 | ## Installation 7 | 8 | ``` 9 | npm install sheetsu-node --save 10 | ``` 11 | 12 | ## Usage 13 | 14 | ### Generating a Client 15 | 16 | You need to create a new sheetsu function, and populate it with your Sheetsu API URL. You can find this URL on [Sheetsu Dashboard](https://sheetsu.com/your-apis). 17 | 18 | ```js 19 | var sheetsu = require('sheetsu-node'); 20 | 21 | // create a config file 22 | var config = { 23 | address: '020b2c0f', 24 | }; 25 | 26 | // Create new client 27 | var client = sheetsu(config); 28 | 29 | ``` 30 | for ES6 31 | ```js 32 | import sheetsu from 'sheetsu-node'; 33 | 34 | // create a config file 35 | var config = { 36 | address: '020b2c0f', 37 | }; 38 | 39 | // Create new client 40 | var client = sheetsu(config); 41 | ``` 42 | 43 | If you have HTTP Basic Authentication turned on for your API, you should pass `api_key` and `api_secret` here, like: 44 | ```js 45 | // create a config file 46 | var config = { 47 | address: '020b2c0f', 48 | api_key: 'YOUR_API_KEY', 49 | api_secret: 'YOUR_API_SECRET', 50 | }; 51 | 52 | // Create new client 53 | var client = sheetsu(config); 54 | ``` 55 | 56 | ### CRUD 57 | 58 | Sheetsu gives you the ability to use full CRUD on your Google Spreadsheet. Remember to populate the first row of every sheet with column names. You can look at [example spreadsheet](https://docs.google.com/spreadsheets/d/1WTwXrh2ZDXmXATZlQIuapdv4ldyhJGZg7LX8GlzPdZw/edit?usp=sharing). 59 | 60 | ### Create 61 | [Link to docs](https://sheetsu.com/docs#post) 62 | 63 | To add data to Google Spreadsheets, send a hash or an array of hashes. 64 | ```js 65 | // Adds single row 66 | client.create({ id: 7, name: "Glenn", score: "69" }).then(function(data) { 67 | console.log(data); 68 | }, function(err){ 69 | console.log(err); 70 | }); 71 | 72 | /// Adds bunch of rows 73 | rows = [ 74 | { id: 7, name: "Glenn", score: "69" }, 75 | { id: 8, name: "Brian", score: "77" }, 76 | { id: 9, name: "Joe", score: "45" } 77 | ] 78 | client.create(rows).then(function(data) { 79 | console.log(data); 80 | }, function(err){ 81 | console.log(err); 82 | }); 83 | 84 | ``` 85 | 86 | By default, all writes are performed on the first sheet (worksheet). Pass name of a sheet as a 2nd param to add data to other worksheet. 87 | ```js 88 | // Adds single row to worksheet named "Sheet3" 89 | client.create({ id: 7, name: "Glenn", score: "69" }, "Sheet3").then(function(data) { 90 | console.log(data); 91 | }, function(err){ 92 | console.log(err); 93 | }); 94 | 95 | ``` 96 | 97 | On success returns a hash or an array of hashes with created values. 98 | 99 | ### Read 100 | [Link to docs](https://sheetsu.com/docs#get) 101 | 102 | Read the whole sheet 103 | ```js 104 | client.read({ limit, offset, search, sheet }).then(function(data) { 105 | console.log(data); 106 | }, function(err){ 107 | console.log(err); 108 | }); 109 | ``` 110 | 111 | You can pass hash with options 112 | - `limit` - limit number of results 113 | - `offset` - start from N first record 114 | - `search` - hash with search params [(more below)](#search) 115 | - `sheet` - get data from named worksheet 116 | 117 | ```js 118 | // Get first two rows from worksheet named "Sheet2" 119 | client.read({ limit: 2, sheet: "Sheet2" }).then(function(data) { 120 | console.log(data); 121 | }, function(err){ 122 | console.log(err); 123 | }); 124 | 125 | // Get 5th and 6th record from worksheet named "Sheet3" 126 | client.read({ limit: 2, offset: 4, sheet: 'Sheet3' }).then(function(data) { 127 | console.log(data); 128 | }, function(err){ 129 | console.log(err); 130 | }); 131 | ``` 132 | 133 | #### search 134 | [Link to docs](https://sheetsu.com/docs#get_search) 135 | 136 | To get rows that match search criteria, pass a hash with search params 137 | 138 | ```js 139 | // Get all rows where column 'id' is 'foo' and column 'value' is 'bar' 140 | client.read({ search: { id: "foo", value: "bar" } }).then(function(data) { 141 | console.log(data); 142 | }, function(err){ 143 | console.log(err); 144 | }); 145 | 146 | // Get all rows where column 'First name' is 'Peter' and column 'Score' is '42' 147 | client.read({ search: { 'first name': 'Peter', 'Score': 42 } }).then(function(data) { 148 | console.log(data); 149 | }, function(err){ 150 | console.log(err); 151 | }); 152 | 153 | 154 | // Get first two row where column 'First name' is 'Peter', 155 | // column 'Score' is '42' from sheet named "Sheet3" 156 | client.read({ 157 | limit: 2, 158 | search: { 'first name': 'Peter', 'Score': 42 }, 159 | sheet: 'Sheet3' 160 | }).then(function(data) { 161 | console.log(data); 162 | }, function(err){ 163 | console.log(err); 164 | }); 165 | 166 | ``` 167 | 168 | On success returns an array of hashes. 169 | 170 | ### Update 171 | [Link to docs](https://sheetsu.com/docs#patch) 172 | 173 | To update row(s), pass column name and its value which is used to find row(s). 174 | 175 | ``` js 176 | client.update(columnName, value, newRow, updateWhole, sheet).then(function(data) { 177 | console.log(data); 178 | }, function(err){ 179 | console.log(err); 180 | }); 181 | ``` 182 | 183 | ```js 184 | // Update all columns where 'name' is 'Peter' to have 'score' = 99 and 'last name' = 'Griffin' 185 | client.update( 186 | 'name', // column name 187 | 'Peter', // value to search for 188 | { 'score': 99, 'last name': 'Griffin' } // hash with updates 189 | ).then(function(data) { 190 | console.log(data); 191 | }, function(err){ 192 | console.log(err); 193 | }); 194 | ``` 195 | 196 | By default, [PATCH request](https://sheetsu.com/docs#patch) is sent, which is updating only values which are in the hash passed to the method. To send [PUT request](https://sheetsu.com/docs#put), pass 4th argument being `true`. [Read more about the difference between PUT and PATCH in our docs](https://sheetsu.com/docs#patch). 197 | 198 | 199 | ```js 200 | // Update all columns where 'name' is 'Peter' to have 'score' = 99 and 'last name' = 'Griffin' 201 | // Empty all cells which matching, which are not 'score' or 'last name' 202 | client.update( 203 | 'name', // column name 204 | 'Peter', // value to search for 205 | { 'score': 99, 'last name': 'Griffin' }, // hash with updates 206 | true // nullify all fields not passed in the hash above 207 | ).then(function(data) { 208 | console.log(data); 209 | }, function(err){ 210 | console.log(err); 211 | }); 212 | 213 | ``` 214 | 215 | To perform `#update` on different than the first sheet, pass sheet name as a 5th argument. 216 | ```js 217 | // Update all columns where 'name' is 'Peter' to have 'score' = 99 and 'last name' = 'Griffin' 218 | // In sheet named 'Sheet3' 219 | // Empty all cells which matching, which are not 'score' or 'last name' 220 | client.update( 221 | 'name', // column name 222 | 'Peter', // value to search for 223 | { 'score': 99, 'last name': 'Griffin' }, // hash with updates 224 | true, // nullify all fields not passed in the hash above 225 | 'Sheet3' 226 | ).then(function(data) { 227 | console.log(data); 228 | }, function(err){ 229 | console.log(err); 230 | }); 231 | ``` 232 | 233 | On success returns an array of hashes with updated values. 234 | 235 | ### Delete 236 | [Link to docs](https://sheetsu.com/docs#delete) 237 | 238 | To delete row(s), pass column name and its value which is used to find row(s). 239 | 240 | ```js 241 | // Delete all rows where 'name' equals 'Peter' 242 | client.delete( 243 | 'name', // column name 244 | 'Peter' // value to search for 245 | ).then(function(data) { 246 | console.log(data); 247 | }, function(err){ 248 | console.log(err); 249 | }); 250 | ``` 251 | 252 | You can pass sheet name as a 3rd argument. All operations are performed on the first sheet, by default. 253 | ```js 254 | // Delete all rows where 'foo' equals 'bar' in sheet 'Sheet3' 255 | client.delete( 256 | 'name', // column name 257 | 'Peter', // value to search for 258 | 'Sheet3' 259 | ).then(function(data) { 260 | console.log(data); 261 | }, function(err){ 262 | console.log(err); 263 | }); 264 | ``` 265 | 266 | If success returns `:ok` symbol. 267 | 268 | ## Development 269 | 270 | Run all tests: 271 | ``` 272 | npm test 273 | ``` 274 | 275 | Run a nyan version test: 276 | ``` 277 | npm run nyan-test 278 | ``` 279 | 280 | ## Contributing 281 | 282 | Bug reports and pull requests are welcome on GitHub at https://github.com/sheetsu/sheetsu-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 283 | 284 | ### Pull Requests 285 | 286 | - **Add tests!** Your patch won't be accepted if it doesn't have tests. 287 | 288 | - **Create topic branches**. Please, always create a branch with meaningful name. Don't ask us to pull from your master branch. 289 | 290 | - **One pull request per feature**. If you want to do more than one thing, please send 291 | multiple pull requests. 292 | 293 | - **Send coherent history**. Make sure each individual commit in your pull 294 | request is meaningful. If you had to make multiple intermediate commits while 295 | developing, please squash them before sending them to us. 296 | 297 | ### Docs 298 | 299 | [Sheetsu documentation sits on GitHub](https://github.com/sheetsu/docs). We would love your contributions! We want to make these docs accessible and easy to understand for everyone. Please send us Pull Requests or open issues on GitHub. 300 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // sheetsu = require(sheetsu-node); 2 | // (ES6+) import sheetsu from 'sheetsu-node'; 3 | // 4 | // sheetsu.create(newRow, sheet); 5 | // sheetsu.read(limit, offset, search, sheet); 6 | // sheetsu.update(columnName, value, newRow, updateWhole, sheet); 7 | // sheetsu.delete(columnName, value, sheet); 8 | 9 | var readFunc = require('./lib/read.js'); 10 | var createFunc = require('./lib/create.js'); 11 | var updateFunc = require('./lib/update.js'); 12 | var deleteFunc = require('./lib/delete.js'); 13 | var validAddress = require('./lib/validAddress.js'); 14 | var isURL = require('./lib/isURL.js'); 15 | 16 | 17 | var sheetsuNode = function(config) { 18 | var configParam = config || {}; 19 | 20 | configParam.version = configParam.version || '1.0on'; 21 | configParam.api_key = configParam.api_key || ''; 22 | configParam.api_secret = configParam.api_secret || ''; 23 | 24 | if(!configParam.address) { 25 | throw Error('address param needed'); 26 | } 27 | 28 | if(!validAddress(configParam.address)) { 29 | throw Error('wrong address param.'); 30 | } 31 | 32 | if(!isURL(configParam.address)) { 33 | configParam.address = 'https://sheetsu.com/apis/v' + 34 | configParam.version + '/' + 35 | configParam.address; 36 | } 37 | 38 | var address = configParam.address; 39 | 40 | return { 41 | config: configParam, 42 | create: createFunc, 43 | read: readFunc, 44 | update: updateFunc, 45 | delete: deleteFunc, 46 | } 47 | } 48 | 49 | module.exports = sheetsuNode; 50 | -------------------------------------------------------------------------------- /lib/create.js: -------------------------------------------------------------------------------- 1 | var btoa = require('btoa'); 2 | if(typeof window != 'undefined') { 3 | XMLHttpRequest = require('xhr2'); 4 | } 5 | 6 | module.exports = function(newRow, sheet) { 7 | var config = this.config; 8 | 9 | return new Promise(function(resolve, reject) { 10 | var xhr = new XMLHttpRequest(); 11 | var sheetParam = (!sheet) ? '' : '/sheets/' + sheet; 12 | var isArray = Array.isArray(newRow); 13 | var data; 14 | 15 | if (isArray) { 16 | data = JSON.stringify({ 17 | rows: newRow, 18 | }); 19 | } else { 20 | data = JSON.stringify(newRow); 21 | } 22 | 23 | var url = config.address + sheetParam; 24 | 25 | xhr.open('POST', url, true); 26 | xhr.setRequestHeader("Accept", "application/vnd.sheetsu.3+json"); 27 | xhr.setRequestHeader("Content-Type", "application/json"); 28 | xhr.setRequestHeader("X-User-Agent", "Sheetsu-Node/"+config.version); 29 | 30 | if (config.api_key && config.api_secret) { 31 | xhr.setRequestHeader("Authorization", "Basic " + btoa(config.api_key+":"+config.api_secret)); 32 | } 33 | 34 | xhr.onload = function (e) { 35 | if (xhr.readyState === 4) { 36 | resolve(xhr.response); 37 | } 38 | }; 39 | 40 | xhr.onerror = function (e) { 41 | reject(e); 42 | }; 43 | xhr.send(data); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /lib/delete.js: -------------------------------------------------------------------------------- 1 | var btoa = require('btoa'); 2 | if(typeof window != 'undefined') { 3 | XMLHttpRequest = require('xhr2'); 4 | } 5 | 6 | module.exports = function(columnName, value, sheet) { 7 | var config = this.config; 8 | 9 | return new Promise(function(resolve, reject) { 10 | var xhr = new XMLHttpRequest(); 11 | 12 | var sheetParam = (!sheet) ? '' : '/sheets/' + sheet; 13 | 14 | if(!columnName) { 15 | reject('no column name'); 16 | } 17 | 18 | var url = config.address + sheetParam + '/' + columnName + '/' + value; 19 | 20 | xhr.open('DELETE', url, true); 21 | 22 | xhr.setRequestHeader("Accept", "application/vnd.sheetsu.3+json"); 23 | xhr.setRequestHeader("Content-Type", "application/json"); 24 | xhr.setRequestHeader("X-User-Agent", "Sheetsu-Node/"+config.version); 25 | 26 | if (config.api_key && config.api_secret) { 27 | xhr.setRequestHeader("Authorization", "Basic " + btoa(config.api_key+":"+config.api_secret)); 28 | } 29 | 30 | xhr.onload = function (e) { 31 | if (xhr.readyState === 4) { 32 | resolve(xhr.response); 33 | } 34 | }; 35 | 36 | xhr.onerror = function (e) { 37 | reject(e); 38 | }; 39 | 40 | xhr.send(); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lib/isURL.js: -------------------------------------------------------------------------------- 1 | module.exports = function(address) { 2 | var pattern = new RegExp("^https:\/\/"); 3 | var res = pattern.test(address); 4 | 5 | return res; 6 | } 7 | -------------------------------------------------------------------------------- /lib/read.js: -------------------------------------------------------------------------------- 1 | var btoa = require('btoa'); 2 | if(typeof window === 'undefined') { 3 | XMLHttpRequest = require('xhr2'); 4 | } 5 | 6 | module.exports = function(params) { 7 | var config = this.config, 8 | params = params ? params : {}; 9 | 10 | return new Promise(function(resolve, reject) { 11 | 12 | var limit = params.limit, 13 | offset = params.offset, 14 | search = params.search, 15 | sheet = params.sheet, 16 | xhr = new XMLHttpRequest(), 17 | limitSign = (search) ? '&' : '?', 18 | offsetSign = (search || limit) ? '&' : '?', 19 | sheetParam = (!sheet) ? '' : '/sheets/' + sheet, 20 | limitParam = (!limit) ? '' : limitSign + 'limit=' + limit, 21 | offsetParam = (!offset) ? '' : offsetSign + 'offset=' + offset, 22 | searchParam = (!search) ? '' : '/search', 23 | searchKeys = (!search) ? [] : Object.keys(search); 24 | 25 | for (var i = 0; i < searchKeys.length; i++) { 26 | var searchValue = search[searchKeys[i]]; 27 | 28 | if(i === 0){ 29 | searchParam += '?' + searchKeys[i] + '=' + searchValue; 30 | } else { 31 | searchParam += '&' + searchKeys[i] + '=' + searchValue; 32 | } 33 | } 34 | 35 | var url = config.address + sheetParam + searchParam + limitParam + offsetParam; 36 | 37 | xhr.open('GET', url, true); 38 | 39 | xhr.setRequestHeader("Accept", "application/vnd.sheetsu.3+json"); 40 | xhr.setRequestHeader("Content-Type", "application/json"); 41 | xhr.setRequestHeader("X-User-Agent", "Sheetsu-Node/"+config.version); 42 | 43 | if (config.api_key && config.api_secret) { 44 | xhr.setRequestHeader("Authorization", "Basic " + btoa(config.api_key+":"+config.api_secret)); 45 | } 46 | 47 | xhr.onload = function (e) { 48 | if (xhr.readyState === 4) { 49 | resolve(xhr.response); 50 | } 51 | }; 52 | 53 | xhr.onerror = function (e) { 54 | reject(e); 55 | }; 56 | 57 | xhr.send(); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /lib/update.js: -------------------------------------------------------------------------------- 1 | var btoa = require('btoa'); 2 | if(typeof window != 'undefined') { 3 | XMLHttpRequest = require('xhr2'); 4 | } 5 | 6 | module.exports = function(columnName, value, newRow, updateWhole, sheet) { 7 | var config = this.config; 8 | 9 | return new Promise(function(resolve, reject) { 10 | var xhr = new XMLHttpRequest(); 11 | var sheetParam = (!sheet) ? '' : '/sheets/' + sheet; 12 | var data = JSON.stringify(newRow); 13 | 14 | if(!columnName) { 15 | reject('no column name'); 16 | } 17 | 18 | var url = config.address + sheetParam + '/' + columnName + '/' + value; 19 | 20 | xhr.open(updateWhole ? 'PUT' : 'PATCH', url, true); 21 | 22 | xhr.setRequestHeader("Accept", "application/vnd.sheetsu.3+json"); 23 | xhr.setRequestHeader("Content-Type", "application/json"); 24 | xhr.setRequestHeader("X-User-Agent", "Sheetsu-Node/"+config.version); 25 | 26 | if (config.api_key && config.api_secret) { 27 | xhr.setRequestHeader("Authorization", "Basic " + btoa(config.api_key+":"+config.api_secret)); 28 | } 29 | 30 | xhr.onload = function (e) { 31 | if (xhr.readyState === 4) { 32 | resolve(xhr.response); 33 | } 34 | }; 35 | 36 | xhr.onerror = function (e) { 37 | reject(e); 38 | }; 39 | 40 | xhr.send(data); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lib/validAddress.js: -------------------------------------------------------------------------------- 1 | module.exports = function(address) { 2 | var pattern = new RegExp("^https:\/\/sheetsu.com"); 3 | var res = pattern.test(address); 4 | 5 | return res || address.indexOf('http') === -1 ; 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Piotr Borysowski", 3 | "contributors": [ 4 | { 5 | "name": "Michael Oblak", 6 | "email": "m@sheetsu.com" 7 | } 8 | ], 9 | "name": "sheetsu-node", 10 | "description": "Official Sheetsu API bindings for Node.js", 11 | "version": "0.0.7", 12 | "homepage": "https://github.com/sheetsu/sheetsu-node", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/sheetsu/sheetsu-node.git" 16 | }, 17 | "keywords": [ 18 | "sheetsu", 19 | "api", 20 | "google spreadsheets", 21 | "google spreadsheets api", 22 | "spreadsheets", 23 | "spreadsheets api" 24 | ], 25 | "scripts": { 26 | "test": "mocha test/", 27 | "nyan-test": "mocha test/ --reporter nyan" 28 | }, 29 | "dependencies": { 30 | "btoa": "^1.1.2", 31 | "xhr2": "^0.1.4", 32 | "xmlhttprequest": "^1.8.0" 33 | }, 34 | "devDependencies": { 35 | "mocha": "3.0.0", 36 | "xhr-mock": "^1.7.0" 37 | }, 38 | "main": "index", 39 | "optionalDependencies": {}, 40 | "engines": { 41 | "node": "*" 42 | }, 43 | "bugs": { 44 | "url": "https://github.com/sheetsu/sheetsu-node/issues" 45 | }, 46 | "license": "ISC" 47 | } 48 | -------------------------------------------------------------------------------- /test/create.spec.js: -------------------------------------------------------------------------------- 1 | var sheetsuAPI = require('../'); 2 | var assert = require('assert'); 3 | 4 | var mock = require('xhr-mock'); 5 | 6 | describe('sheetsu', function() { 7 | describe('create()', function() { 8 | var sheetsu = sheetsuAPI({ 9 | address: 'dfsdf43fsd', 10 | }); 11 | 12 | it('should run with POST method', function() { 13 | mock.setup(); 14 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 15 | return res.status(201).body('test'); 16 | }); 17 | 18 | return sheetsu.create({}).then(function(data) { 19 | assert.equal(data, 'test'); 20 | }, function(err) { 21 | console.log(err) 22 | assert.fail('sheetsu throw error'); 23 | }).then(function() { 24 | mock.teardown(); 25 | }); 26 | }); 27 | 28 | it('should run with Http Basic Auth', function() { 29 | mock.setup(); 30 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 31 | return res.status(201).body(req._headers); 32 | }); 33 | 34 | sheetsuLocal = sheetsuAPI({ 35 | address: 'dfsdf43fsd', 36 | api_key: 'somekey', 37 | api_secret: 'somesecret', 38 | }); 39 | 40 | return sheetsuLocal.create({}).then(function(data) { 41 | assert.equal(data.authorization, "Basic c29tZWtleTpzb21lc2VjcmV0"); 42 | }, function(err) { 43 | assert.fail('sheetsu throw error'); 44 | }).then(function(){ 45 | mock.teardown(); 46 | }); 47 | }); 48 | 49 | it('should run with correct headers', function() { 50 | mock.setup(); 51 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 52 | return res.status(201).body(req._headers); 53 | }); 54 | 55 | return sheetsu.create({}).then(function(data) { 56 | assert.equal(data["accept"], "application/vnd.sheetsu.3+json"); 57 | assert.equal(data["content-type"], "application/json"); 58 | assert.equal(data["x-user-agent"], "Sheetsu-Node/1.0on"); 59 | }, function(err) { 60 | assert.fail('sheetsu throw error'); 61 | }).then(function(){ 62 | mock.teardown(); 63 | }); 64 | }); 65 | 66 | it('should run with object data', function() { 67 | mock.setup(); 68 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 69 | return res.status(201).body('{"some":5}'); 70 | }); 71 | 72 | return sheetsu.create({some: 5}).then(function(data){ 73 | assert.equal(data, '{"some":5}'); 74 | }, function(err) { 75 | assert.fail('sheetsu throw error'); 76 | }).then(function(){ 77 | mock.teardown(); 78 | }); 79 | }); 80 | 81 | it('should run with array data', function() { 82 | mock.setup(); 83 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 84 | return res.status(201).body('{"rows":[{},{"test":3}]}'); 85 | }); 86 | 87 | return sheetsu.create([{}, {test: 3}]).then(function(data){ 88 | assert.equal(data, '{"rows":[{},{"test":3}]}'); 89 | }, function(err) { 90 | assert.fail('sheetsu throw error'); 91 | }).then(function(){ 92 | mock.teardown(); 93 | }); 94 | }); 95 | 96 | it('should return correct url', function() { 97 | mock.setup(); 98 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 99 | return res.status(201).body(req); 100 | }); 101 | 102 | return sheetsu.create({}).then(function(data){ 103 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd'); 104 | }, function(err) { 105 | assert.fail('sheetsu throw error'); 106 | }).then(function(){ 107 | mock.teardown(); 108 | }); 109 | }); 110 | 111 | it('should return url different Sheet', function() { 112 | mock.setup(); 113 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3', function(req, res) { 114 | return res.status(201).body(req); 115 | }); 116 | 117 | return sheetsu.create({}, 'Sheet3').then(function(data){ 118 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3'); 119 | }, function(err) { 120 | assert.fail('sheetsu throw error'); 121 | }); 122 | }); 123 | 124 | it('should return error when 404', function() { 125 | mock.setup(); 126 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 127 | return res.status(404).body(req); 128 | }); 129 | 130 | return sheetsu.create({}).then(function(data) { 131 | assert.fail('sheetsu does not throw any error'); 132 | }, function(err) { 133 | }).then(function(){ 134 | mock.teardown(); 135 | }); 136 | }); 137 | 138 | it('should return error when 429', function() { 139 | mock.setup(); 140 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 141 | return res.status(429).body(req); 142 | }); 143 | 144 | return sheetsu.create({}).then(function(data) { 145 | assert.fail('sheetsu does not throw any error'); 146 | }, function(err) { 147 | }).then(function(){ 148 | mock.teardown(); 149 | }); 150 | }); 151 | 152 | it('should return error when 403', function() { 153 | mock.setup(); 154 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 155 | return res.status(403).body(req); 156 | }); 157 | 158 | return sheetsu.create({}).then(function(data) { 159 | assert.fail('sheetsu does not throw any error'); 160 | }, function(err) { 161 | }).then(function(){ 162 | mock.teardown(); 163 | }); 164 | }); 165 | 166 | it('should return error when 401', function() { 167 | mock.setup(); 168 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 169 | return res.status(401).body(req); 170 | }); 171 | 172 | return sheetsu.create({}).then(function(data) { 173 | assert.fail('sheetsu does not throw any error'); 174 | }, function(err) { 175 | }).then(function(){ 176 | mock.teardown(); 177 | }); 178 | }); 179 | 180 | it('should return error when 500', function() { 181 | mock.setup(); 182 | mock.post('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 183 | return res.status(500).body(req); 184 | }); 185 | 186 | return sheetsu.create({}).then(function(data) { 187 | assert.fail('sheetsu does not throw any error'); 188 | }, function(err) { 189 | }).then(function(){ 190 | mock.teardown(); 191 | }); 192 | }); 193 | 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /test/delete.spec.js: -------------------------------------------------------------------------------- 1 | var sheetsuAPI = require('../'); 2 | var assert = require('assert'); 3 | 4 | var mock = require('xhr-mock'); 5 | 6 | describe('sheetsu', function() { 7 | describe('delete() function', function() { 8 | var sheetsu = sheetsuAPI({ 9 | address: 'dfsdf43fsd', 10 | }); 11 | 12 | it('should run with DELETE method', function() { 13 | mock.setup(); 14 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 15 | return res.status(200).body('test'); 16 | }); 17 | 18 | return sheetsu.delete('column', 'test').then(function(data) { 19 | assert.equal(data, 'test'); 20 | }, function(err) { 21 | assert.fail('sheetsu throw error'); 22 | }).then(function() { 23 | mock.teardown(); 24 | }); 25 | }); 26 | 27 | it('should run with Http Basic Auth', function() { 28 | mock.setup(); 29 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 30 | return res.status(201).body(req._headers); 31 | }); 32 | 33 | sheetsuLocal = sheetsuAPI({ 34 | address: 'dfsdf43fsd', 35 | api_key: 'somekey', 36 | api_secret: 'somesecret', 37 | }); 38 | 39 | return sheetsuLocal.delete('column', 'test').then(function(data) { 40 | assert.equal(data.authorization, "Basic c29tZWtleTpzb21lc2VjcmV0"); 41 | }, function(err) { 42 | assert.fail('sheetsu throw error'); 43 | }).then(function(){ 44 | mock.teardown(); 45 | }); 46 | }); 47 | 48 | it('should run with correct headers', function() { 49 | mock.setup(); 50 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 51 | return res.status(201).body(req._headers); 52 | }); 53 | 54 | return sheetsu.delete('column', 'test').then(function(data) { 55 | assert.equal(data["accept"], "application/vnd.sheetsu.3+json"); 56 | assert.equal(data["content-type"], "application/json"); 57 | assert.equal(data["x-user-agent"], "Sheetsu-Node/1.0on"); 58 | }, function(err) { 59 | assert.fail('sheetsu throw error'); 60 | }).then(function(){ 61 | mock.teardown(); 62 | }); 63 | }); 64 | 65 | it('should run with column name and value', function() { 66 | mock.setup(); 67 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 68 | return res.status(200).body(req); 69 | }) 70 | 71 | return sheetsu.delete('column', 'test').then(function(data){ 72 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test'); 73 | }, function(err) { 74 | assert.fail('sheetsu throw error'); 75 | }).then(function() { 76 | mock.teardown(); 77 | }); 78 | }); 79 | 80 | it('should throw error when no column param', function() { 81 | mock.setup(); 82 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 83 | return res.status(200).body(req); 84 | }); 85 | 86 | return sheetsu.delete().then(function(data){ 87 | assert.fail('sheetsu do not throw error'); 88 | }, function(err) { 89 | assert.equal(err, 'no column name'); 90 | }).then(function() { 91 | mock.teardown(); 92 | }); 93 | }); 94 | 95 | it('should return url different Sheet', function() { 96 | mock.setup(); 97 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3/column/test', function(req, res) { 98 | return res.status(200).body(req); 99 | }); 100 | 101 | return sheetsu.delete('column', 'test', 'Sheet3').then(function(data){ 102 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3/column/test'); 103 | }, function(err) { 104 | assert.fail('sheetsu throw error'); 105 | }).then(function() { 106 | mock.teardown(); 107 | }); 108 | }); 109 | 110 | it('should return error when 404', function() { 111 | mock.setup(); 112 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 113 | return res.status(404).body(req._xhr); 114 | }); 115 | 116 | return sheetsu.delete('column', 'test').then(function(data) { 117 | assert.equal(data.status, 404); 118 | }, function(err) { 119 | }).then(function(){ 120 | mock.teardown(); 121 | }); 122 | }); 123 | 124 | it('should return error when 429', function() { 125 | mock.setup(); 126 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 127 | return res.status(429).body(req._xhr); 128 | }); 129 | 130 | return sheetsu.delete('column', 'test').then(function(data) { 131 | assert.equal(data.status, 429); 132 | }, function(err) { 133 | }).then(function(){ 134 | mock.teardown(); 135 | }); 136 | }); 137 | 138 | it('should return error when 403', function() { 139 | mock.setup(); 140 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 141 | return res.status(403).body(req._xhr); 142 | }); 143 | 144 | return sheetsu.delete('column', 'test').then(function(data) { 145 | assert.equal(data.status, 403); 146 | }, function(err) { 147 | }).then(function(){ 148 | mock.teardown(); 149 | }); 150 | }); 151 | 152 | it('should return error when 401', function() { 153 | mock.setup(); 154 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 155 | return res.status(401).body(req._xhr); 156 | }); 157 | 158 | return sheetsu.delete('column', 'test').then(function(data) { 159 | assert.equal(data.status, 401); 160 | }, function(err) { 161 | }).then(function(){ 162 | mock.teardown(); 163 | }); 164 | }); 165 | 166 | it('should return error when 500', function() { 167 | mock.setup(); 168 | mock.delete('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/column/test', function(req, res) { 169 | return res.status(500).body(req._xhr); 170 | }); 171 | 172 | return sheetsu.delete('column', 'test').then(function(data) { 173 | assert.equal(data.status, 500); 174 | }, function(err) { 175 | }).then(function(){ 176 | mock.teardown(); 177 | }); 178 | }); 179 | 180 | }); 181 | }); 182 | -------------------------------------------------------------------------------- /test/read.spec.js: -------------------------------------------------------------------------------- 1 | var sheetsuAPI = require('../'); 2 | var assert = require('assert'); 3 | 4 | var mock = require('xhr-mock'); 5 | 6 | describe('sheetsu', function() { 7 | describe('read() function', function() { 8 | var sheetsu = sheetsuAPI({ 9 | address: 'dfsdf43fsd', 10 | }); 11 | 12 | it('should run with GET method', function() { 13 | mock.setup(); 14 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 15 | return res.status(200).body('test'); 16 | }); 17 | 18 | return sheetsu.read({}).then(function(data) { 19 | assert.equal(data, 'test'); 20 | }, function(err) { 21 | assert.fail('sheetsu throw error'); 22 | }).then(function() { 23 | mock.teardown(); 24 | }); 25 | }); 26 | 27 | it('should run with Http Basic Auth', function() { 28 | mock.setup(); 29 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 30 | return res.status(200).body(req._headers); 31 | }); 32 | 33 | sheetsuLocal = sheetsuAPI({ 34 | address: 'dfsdf43fsd', 35 | api_key: 'somekey', 36 | api_secret: 'somesecret', 37 | }); 38 | 39 | return sheetsuLocal.read().then(function(data) { 40 | assert.equal(data.authorization, "Basic c29tZWtleTpzb21lc2VjcmV0"); 41 | }, function(err) { 42 | assert.fail('sheetsu throw error'); 43 | }).then(function(){ 44 | mock.teardown(); 45 | }); 46 | }); 47 | 48 | it('should run with correct headers', function() { 49 | mock.setup(); 50 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 51 | return res.status(200).body(req._headers); 52 | }); 53 | 54 | return sheetsu.read().then(function(data) { 55 | assert.equal(data["accept"], "application/vnd.sheetsu.3+json"); 56 | assert.equal(data["content-type"], "application/json"); 57 | assert.equal(data["x-user-agent"], "Sheetsu-Node/1.0on"); 58 | }, function(err) { 59 | assert.fail('sheetsu throw error'); 60 | }).then(function(){ 61 | mock.teardown(); 62 | }); 63 | }); 64 | 65 | it('should return url without limit and offset', function() { 66 | mock.setup(); 67 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd', function(req, res) { 68 | return res.status(200).body(req); 69 | }); 70 | return sheetsu.read().then(function(data){ 71 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd'); 72 | }, function(err) { 73 | assert.fail('sheetsu throw error'); 74 | }).then(function(){ 75 | mock.teardown(); 76 | }); 77 | }); 78 | 79 | it('should return url with limit', function() { 80 | mock.setup(); 81 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd?limit=5', function(req, res) { 82 | return res.status(200).body(req); 83 | }); 84 | 85 | return sheetsu.read({ limit: 5 }).then(function(data){ 86 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd?limit=5'); 87 | }, function(err) { 88 | assert.fail('sheetsu throw error'); 89 | }).then(function(){ 90 | mock.teardown(); 91 | }); 92 | }); 93 | 94 | it('should return url with offset', function() { 95 | mock.setup(); 96 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd?offset=10', function(req, res) { 97 | return res.status(200).body(req); 98 | }); 99 | 100 | return sheetsu.read({ offset: 10 }).then(function(data) { 101 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd?offset=10'); 102 | }, function(err) { 103 | assert.fail('sheetsu throw error'); 104 | }).then(function(){ 105 | mock.teardown(); 106 | }); 107 | }); 108 | 109 | it('should return url with offset and limit', function() { 110 | mock.setup(); 111 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd?limit=5&offset=10', function(req, res) { 112 | return res.status(200).body(req); 113 | }); 114 | 115 | return sheetsu.read({ limit: 5, offset: 10 }).then(function(data){ 116 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd?limit=5&offset=10'); 117 | }, function(err) { 118 | assert.fail('sheetsu throw error'); 119 | }).then(function(){ 120 | mock.teardown(); 121 | }); 122 | }); 123 | 124 | it('should be able to search', function() { 125 | mock.setup(); 126 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/search?name=test&foo=bar', function(req, res) { 127 | return res.status(200).body(req); 128 | }); 129 | 130 | return sheetsu.read({ search: {name: 'test', foo: 'bar'} }).then(function(data) { 131 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd/search?name=test&foo=bar'); 132 | }, function(err) { 133 | assert.fail('sheetsu throw error'); 134 | }).then(function(){ 135 | mock.teardown(); 136 | }); 137 | }); 138 | 139 | it('should be able to search with limit', function() { 140 | mock.setup(); 141 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/search?name=test&foo=bar&limit=5', function(req, res) { 142 | return res.status(200).body(req); 143 | }); 144 | 145 | return sheetsu.read({ limit: 5, search: {name: 'test', foo: 'bar'} }).then(function(data) { 146 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd/search?name=test&foo=bar&limit=5'); 147 | }, function(err) { 148 | assert.fail('sheetsu throw error'); 149 | }).then(function(){ 150 | mock.teardown(); 151 | }); 152 | }); 153 | 154 | it('should be able to use different sheet', function() { 155 | mock.setup(); 156 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3', function(req, res) { 157 | return res.status(200).body(req); 158 | }); 159 | 160 | return sheetsu.read({ sheet: 'Sheet3' }).then(function(data) { 161 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3'); 162 | }, function(err) { 163 | assert.fail('sheetsu throw error'); 164 | }).then(function(){ 165 | mock.teardown(); 166 | }); 167 | }); 168 | 169 | it('should be able to use different sheet when limit set', function() { 170 | mock.setup(); 171 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 172 | return res.status(200).body(req); 173 | }); 174 | 175 | return sheetsu.read({ limit: 6, sheet: 'Sheet3' }).then(function(data) { 176 | assert.equal(data._url, 'https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6'); 177 | }, function(err) { 178 | assert.fail('sheetsu throw error'); 179 | }).then(function(){ 180 | mock.teardown(); 181 | }); 182 | }); 183 | 184 | it('should return error when 404', function() { 185 | mock.setup(); 186 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 187 | return res.status(404).body(req); 188 | }); 189 | 190 | return sheetsu.read().then(function(data) { 191 | assert.fail('sheetsu does not throw any error'); 192 | }, function(err) { 193 | }).then(function(){ 194 | mock.teardown(); 195 | }); 196 | }); 197 | 198 | it('should return error when 429', function() { 199 | mock.setup(); 200 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 201 | return res.status(429).body(req); 202 | }); 203 | 204 | return sheetsu.read().then(function(data) { 205 | assert.fail('sheetsu does not throw any error'); 206 | }, function(err) { 207 | }).then(function(){ 208 | mock.teardown(); 209 | }); 210 | }); 211 | 212 | it('should return error when 403', function() { 213 | mock.setup(); 214 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 215 | return res.status(403).body(req); 216 | }); 217 | 218 | return sheetsu.read().then(function(data) { 219 | assert.fail('sheetsu does not throw any error'); 220 | }, function(err) { 221 | }).then(function(){ 222 | mock.teardown(); 223 | }); 224 | }); 225 | 226 | it('should return error when 401', function() { 227 | mock.setup(); 228 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 229 | return res.status(401).body(req); 230 | }); 231 | 232 | return sheetsu.read().then(function(data) { 233 | assert.fail('sheetsu does not throw any error'); 234 | }, function(err) { 235 | }).then(function(){ 236 | mock.teardown(); 237 | }); 238 | }); 239 | 240 | it('should return error when 500', function() { 241 | mock.setup(); 242 | mock.get('https://sheetsu.com/apis/v1.0on/dfsdf43fsd/sheets/Sheet3?limit=6', function(req, res) { 243 | return res.status(500).body(req); 244 | }); 245 | 246 | return sheetsu.read().then(function(data) { 247 | assert.fail('sheetsu does not throw any error'); 248 | }, function(err) { 249 | }).then(function(){ 250 | mock.teardown(); 251 | }); 252 | }); 253 | }); 254 | }); 255 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var sheetsuAPI = require('../'); 2 | var assert = require('assert'); 3 | var mock = require('xhr-mock'); 4 | describe('sheetsu', function() { 5 | describe('contructor', function() { 6 | it('should throw error when param has no address', function() { 7 | assert.throws(function() { 8 | sheetsuAPI(); 9 | }, Error); 10 | }); 11 | 12 | it('should not throw error when param has valid address', function() { 13 | mock.get('https://sheetsu.com', function(req, res) { 14 | return res.status(200).body('