├── .nvmrc ├── .gitattributes ├── index.js ├── .gitignore ├── CHANGELOG.md ├── .travis.yml ├── test ├── clock.mock.js ├── https-request.mock.js ├── bucket-events.test.js └── bucket.test.js ├── .editorconfig ├── node-initial-state.sublime-project ├── https-request.js ├── gulpfile.js ├── package.json ├── epoch-clock.js ├── LICENSE ├── .eslintrc ├── README.md └── bucket.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.12 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.bucket = require('./bucket'); 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | node_modules 4 | *.sublime-workspace 5 | *.log 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.0 4 | 5 | * initial state …of code 6 | * Incremented personal "use service name as pun" tally 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | git: 3 | depth: 2 4 | language: node_js 5 | node_js: 6 | - iojs 7 | - node 8 | after_script: 9 | - npm test 10 | -------------------------------------------------------------------------------- /test/clock.mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | function now() { 5 | return '1420070400.000000000'; 6 | } 7 | exports.now = now; 8 | 9 | function toTimestamp(value) { 10 | if (value != null && isFinite(+value) || typeof value === 'undefined') { 11 | return now(); 12 | } 13 | return void 0; 14 | } 15 | 16 | exports.toTimestamp = toTimestamp; 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | 12 | [*.js] 13 | curly_bracket_next_line = false 14 | spaces_around_operators = true 15 | indent_brace_style = 1TBS 16 | spaces_around_brackets = both 17 | -------------------------------------------------------------------------------- /node-initial-state.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "file_exclude_patterns": 6 | [ 7 | "*.sublime-project" 8 | ], 9 | "folder_exclude_patterns": 10 | [ 11 | "node_modules" 12 | ], 13 | "follow_symlinks": true, 14 | "path": "." 15 | } 16 | ], 17 | "SublimeLinter": 18 | { 19 | "linters": 20 | { 21 | "jshint": 22 | { 23 | "@disable": true 24 | }, 25 | "eslint": 26 | { 27 | "@disable": false 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /https-request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var https = require('https'); 4 | 5 | module.exports = function request(config, data, done) { 6 | https.request(config, function (res) { 7 | var err = null, 8 | status = res.statusCode; 9 | 10 | // TODO: handle 429 responses 11 | if (status === 401) { 12 | err = new Error('Unauthorized'); 13 | } else if (status < 200 || status >= 300) { 14 | err = new Error('API Response Status ' + status); 15 | } 16 | done(err, status); 17 | }) 18 | .on('error', done) 19 | .end(JSON.stringify(data)); 20 | }; 21 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | gulp.task('lint', function () { 6 | var eslint = require('gulp-eslint'); 7 | 8 | return gulp.src('*.js') 9 | .pipe(eslint()) 10 | .pipe(eslint.format()) 11 | .pipe(eslint.failAfterError()); 12 | }); 13 | 14 | gulp.task('test', ['lint'], function () { 15 | var mocha = require('gulp-mocha'); 16 | 17 | return gulp.src('test/*.test.js', {read: false}) 18 | .pipe(mocha({ 19 | reporter: 'spec', 20 | timeout: 2000 21 | })); 22 | }); 23 | 24 | gulp.task('default', ['test']); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "initial-state", 3 | "version": "0.2.0", 4 | "description": "Initial State SDK", 5 | "main": "index.js", 6 | "keywords": [ 7 | "initial state", 8 | "sdk" 9 | ], 10 | "author": "InitialState", 11 | "license": "MIT", 12 | "homepage": "https://github.com/InitialState/node-initial-state", 13 | "repository": "InitialState/node-initial-state", 14 | "bugs": { 15 | "url": "https://github.com/InitialState/node-initial-state/issues" 16 | }, 17 | "scripts": { 18 | "test": "gulp test" 19 | }, 20 | "devDependencies": { 21 | "chai": "^3.2.0", 22 | "gulp": "^3.6.2", 23 | "gulp-eslint": "^1.0.0", 24 | "gulp-mocha": "^2.1.3", 25 | "mocha": "^2.3.0", 26 | "mockery": "^1.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /epoch-clock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var epoch = Date.now(), 4 | start = process.hrtime(); 5 | 6 | function now() { 7 | // get unix timestamp (seconds since epoch) with high-precision 8 | var elapsed = process.hrtime(start), 9 | ms = elapsed[1] / 1e6, 10 | subms = ms % 1; 11 | return ((epoch + ms - subms) / 1e3 + elapsed[0]).toFixed(3) + subms.toFixed(6).slice(2); 12 | } 13 | exports.now = now; 14 | 15 | function toTimestamp(value) { 16 | if (typeof value === 'string') { 17 | if (isFinite(+value)) { 18 | // Accept pre-defined 19 | return value; 20 | } 21 | value = Date.parse(value); 22 | } 23 | if (typeof value === 'number' && isFinite(value)) { 24 | // ms -> sec 25 | return (value / 1000).toString(); 26 | } 27 | if (typeof value === 'undefined') { 28 | return now(); 29 | } 30 | return void 0; 31 | } 32 | exports.toTimestamp = toTimestamp; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Initial State Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/https-request.mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Mock Triggers & Response Status 4 | 5 | // Access Keys: 6 | // invalid-key — 401 7 | // error-key — 503 8 | 9 | // Bucket IDs: 10 | // new-id — 201 11 | // invalid:id — 401 12 | // overload-id — 429 13 | 14 | function initBucketStatus(config, data) { 15 | var id = data && data.bucketKey; 16 | return (id === 'invalid:id') ? 400 : 17 | (id === 'overload-id') ? 429 : 18 | (id === 'new-id') ? 201 : 200; 19 | } 20 | 21 | function postEventsStatus(config, data) { 22 | var id = config.headers && config.headers['X-IS-AccessKey']; 23 | return (id === 'invalid:id') ? 401 : 24 | (id === 'overload-id') ? 429 : 25 | Array.isArray(data) ? 200 : 400; 26 | } 27 | 28 | module.exports = function mockHttpsRequest(config, data, done) { 29 | var key = config && config.headers && config.headers['X-IS-AccessKey'], 30 | status = (!data) ? 400: 31 | (key === 'invalid-key' || !key) ? 401: 32 | (key === 'error-key') ? 503: 33 | data.bucketKey ? 34 | initBucketStatus(config, data, done): 35 | postEventsStatus(config, data, done), 36 | err = (status === 401) ? new Error('Unauthorized'): 37 | (status < 200 || status >= 300) ? new Error('API Response Status ' + status): null; 38 | 39 | process.nextTick(function () { 40 | done(err, status); 41 | }); 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | root: 2 | true 3 | 4 | env: 5 | node: true 6 | es6: true 7 | 8 | rules: 9 | comma-dangle: 2 10 | no-cond-assign: 2 11 | no-console: 1 12 | no-constant-condition: 2 13 | no-control-regex: 2 14 | no-debugger: 2 15 | no-dupe-args: 2 16 | no-dupe-keys: 2 17 | no-duplicate-case: 2 18 | no-empty: 2 19 | no-ex-assign: 2 20 | no-extra-boolean-cast: 2 21 | no-func-assign: 2 22 | no-inner-declarations: 2 23 | no-invalid-regexp: 2 24 | no-irregular-whitespace: 2 25 | no-negated-in-lhs: 2 26 | no-obj-calls: 2 27 | no-regex-spaces: 2 28 | no-sparse-arrays: 2 29 | no-unreachable: 2 30 | use-isnan: 2 31 | valid-typeof: 2 32 | no-fallthrough: 2 33 | no-octal: 2 34 | no-redeclare: 2 35 | no-delete-var: 2 36 | no-undef: 2 37 | no-unused-vars: 1 38 | no-mixed-spaces-and-tabs: 2 39 | radix: 2 40 | wrap-iife: 2 41 | guard-for-in: 2 42 | no-alert: 2 43 | new-cap: 2 44 | no-new: 2 45 | no-caller: 2 46 | strict: 47 | - 2 48 | - "global" 49 | eqeqeq: 50 | - 2 51 | - "allow-null" 52 | no-extra-semi: 1 53 | semi: 1 54 | no-shadow: 1 55 | camelcase: 1 56 | no-floating-decimal: 1 57 | space-before-blocks: 1 58 | eol-last: 1 59 | key-spacing: 1 60 | curly: 61 | - 1 62 | - "multi-line" 63 | indent: 64 | - 1 65 | - "tab" 66 | brace-style: 67 | - 1 68 | - "1tbs" 69 | comma-style: 70 | - 1 71 | - "last" 72 | keyword-spacing: 73 | - 1 74 | - { "after": true } 75 | valid-jsdoc: 76 | - 1 77 | - prefer: 78 | return: "returns" 79 | no-use-before-define: 80 | - 1 81 | - "nofunc" 82 | semi-spacing: 83 | - 1 84 | - before: false 85 | after: true 86 | quotes: 87 | - 1 88 | - "single" 89 | - "avoid-escape" 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Initial State 2 | The Initial State SDK for NodeJS. 3 | 4 | [![Build Status](https://travis-ci.org/initialstate/node-initial-state.svg)](https://travis-ci.org/initialstate/node-initial-state) 5 | 6 | ## Installation 7 | 8 | ```sh 9 | npm install initial-state 10 | ``` 11 | 12 | ## Example Use 13 | 14 | ```javascript 15 | var IS = require('initial-state'); 16 | var bucket = IS.bucket('BUCKET_KEY', 'YOUR_ACCESS_KEY_GOES_HERE'); 17 | 18 | // Push a count every second 19 | var count = 0; 20 | setTimeout(function pushNextCount() { 21 | 22 | // Push another event 23 | bucket.push('Count', ++count); 24 | 25 | if (count < 10) { 26 | // Keep counting until we reach 10 27 | setTimeout(pushNextCount, 1000); 28 | } 29 | 30 | }, 1000); 31 | ``` 32 | 33 | ## API 34 | 35 | #### IS.bucket(id, accessKey) 36 | 37 | Create an event data bucket. 38 | 39 | * *id* – A bucket key. This key should contain only alphanumeric and underscore characters. If the bucket does not yet exist, this value will be used as the bucket name. 40 | * *accessKey* – An Initial State account access key. This argument is not needed if the access key is assigned to the environmental variable `IS_API_ACCESS_KEY`. 41 | 42 | To declare a different bucket key and name, use the object override parameter: 43 | 44 | ```javascript 45 | var bucket = IS.bucket({ 46 | name: 'My Bucket', 47 | id: 'BUCKET_KEY', 48 | accessKey: 'YOUR_ACCESS_KEY_GOES_HERE' 49 | }); 50 | ``` 51 | 52 | ### bucket.push(key, value[, date]) 53 | 54 | Send event data to Initial State. 55 | 56 | * *key* – The event key. 57 | * *value* – The event value. 58 | * *date* – The time of the event. A `Date` object or numeric timestamp (milliseconds since epoch). High-precision timestamps (i.e., sub-ms) can be declared as a string (e.g., `'1420070400.000000001'`), but must be in unix time (seconds since epoch). If not defined, a high-precision timestamp will be generated. 59 | -------------------------------------------------------------------------------- /bucket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | var request = require('./https-request'); 5 | var Clock = require('./epoch-clock'); 6 | var API_HOST = 'groker.initialstate.com'; 7 | 8 | 9 | function Bucket(name, id, accessKey) { 10 | if (!(this instanceof Bucket)) { 11 | return new Bucket(name, id, accessKey); 12 | } 13 | 14 | EventEmitter.call(this); 15 | 16 | var bucket = this, 17 | buffer = [], 18 | verified = false, 19 | sending = false, 20 | flushSymbol = null, 21 | bucketData = { 22 | bucketKey: id || name, 23 | bucketName: name || id 24 | }, 25 | eventHttp = { 26 | hostname: process.env.IS_API_HOST || API_HOST, 27 | path: '/api/buckets', 28 | method: 'POST', 29 | keepAlive: true, 30 | headers: { 31 | 'Content-Type': 'application/json; charset=utf-8', 32 | 'Accept-Version': '0.0.1', 33 | 'X-IS-AccessKey': accessKey 34 | } 35 | }; 36 | 37 | bucket.name = bucketData.bucketName; 38 | bucket.id = bucketData.bucketKey; 39 | 40 | function pushEvent(event) { 41 | if (event) { 42 | buffer.push(event); 43 | flushSoon(); 44 | } 45 | } 46 | 47 | this.push = function (key, value, date) { 48 | pushEvent({ 49 | key: (typeof key === 'string') ? key : String(key), 50 | value: (typeof value === 'string') ? value : String(value), 51 | epoch: Clock.toTimestamp(date) 52 | }); 53 | }; 54 | 55 | function sent(err) { 56 | sending = false; 57 | // events sent, ready to send more... 58 | if (err) { 59 | bucket.emit('error', err); 60 | return; 61 | } 62 | if (buffer.length > 0) { 63 | flushSoon(); 64 | } else { 65 | bucket.emit('drain'); 66 | } 67 | } 68 | 69 | function flush() { 70 | // send all events 71 | clearImmediate(flushSymbol); 72 | flushSymbol = null; 73 | var len = buffer.length; 74 | if (verified && len > 0) { 75 | sending = true; 76 | var events = buffer.splice(0, len); 77 | request(eventHttp, events, sent); 78 | bucket.emit('data', events); 79 | } 80 | } 81 | 82 | function flushSoon() { 83 | // queue new flush, only after flush has completed 84 | if (verified && !sending && flushSymbol == null) { 85 | flushSymbol = setImmediate(flush); 86 | } 87 | } 88 | 89 | // Create/verify bucket before sending data 90 | request(eventHttp, bucketData, function (err, status) { 91 | if (err) { 92 | bucket.emit('error', err); 93 | return; 94 | } 95 | 96 | // Switch to events endpoint 97 | eventHttp.path = '/api/events'; 98 | eventHttp.headers['X-IS-BucketKey'] = bucketData.bucketKey; 99 | 100 | // bucket exists, start sending events 101 | verified = true; 102 | if (buffer.length > 0) { 103 | flushSoon(); 104 | } 105 | 106 | bucket.emit('ready', status === 201); 107 | }); 108 | } 109 | 110 | Bucket.prototype = Object.create(EventEmitter.prototype, { 111 | constructor: { value: Bucket } 112 | }); 113 | 114 | module.exports = function createBucket(id, accessKey) { 115 | // (id/name, accessKey) 116 | var name = id; 117 | if (id != null && typeof id !== 'string' && typeof id.id === 'string') { 118 | // ({ name, id }, accessKey) || ({ id, name, accessKey }) 119 | name = (typeof id.name === 'string') ? id.name : id.id; 120 | if (typeof id.accessKey === 'string' && !accessKey) { 121 | accessKey = id.accessKey; 122 | } 123 | id = id.id; 124 | } 125 | 126 | return new Bucket(name, id, accessKey || process.env.IS_API_ACCESS_KEY || ''); 127 | }; 128 | 129 | module.exports.Bucket = Bucket; 130 | -------------------------------------------------------------------------------- /test/bucket-events.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var does = require('mocha'), 4 | mockery = require('mockery'), 5 | assert = require('chai').assert; 6 | 7 | 8 | does.describe('Events', function () { 9 | 10 | var bucket, 11 | now; 12 | 13 | // Set Up 14 | 15 | does.before(function () { 16 | 17 | mockery.enable({ 18 | warnOnReplace: true, 19 | warnOnUnregistered: true, 20 | useCleanCache: true 21 | }); 22 | 23 | mockery.registerAllowable('../bucket'); 24 | mockery.registerAllowable('events'); 25 | mockery.registerAllowable('./clock.mock'); 26 | mockery.registerSubstitute('./epoch-clock', './test/clock.mock'); 27 | mockery.registerSubstitute('./https-request', './test/https-request.mock'); 28 | 29 | now = require('./clock.mock').now(); 30 | 31 | }); 32 | 33 | does.beforeEach(function (done) { 34 | 35 | var isBucket = require('../bucket'); 36 | 37 | bucket = isBucket('valid-id', 'valid-key') 38 | .once('ready', function () { 39 | bucket.removeListener('error', done); 40 | done(); 41 | }) 42 | .on('error', done); 43 | 44 | }); 45 | 46 | // Tear Down 47 | 48 | does.afterEach(function () { 49 | 50 | if (bucket) { 51 | bucket.removeAllListeners(); 52 | } 53 | 54 | }); 55 | 56 | does.after(function () { 57 | mockery.deregisterAll(); 58 | mockery.disable(); 59 | }); 60 | 61 | // Tests 62 | 63 | does.it('push an event', function (done) { 64 | 65 | var count = 0; 66 | 67 | bucket.push('event-type', 'event-value', now); 68 | 69 | bucket.once('data', function (events) { 70 | assert.isArray(events); 71 | assert.strictEqual(events[0].key, 'event-type', 'Event has expected "key"'); 72 | assert.strictEqual(events[0].value, 'event-value', 'Event has expected "value"'); 73 | assert.strictEqual(events[0].epoch, now, 'Event has expected "epoch"'); 74 | 75 | count += events.length; 76 | }) 77 | .once('drain', function () { 78 | assert.strictEqual(count, 1, 'One event was sent'); 79 | done(); 80 | }) 81 | .once('error', done); 82 | 83 | }); 84 | 85 | does.it('send multiple events at once', function (done) { 86 | 87 | var count = 0, 88 | values = ['A','B','C','D','E','F','G','H','I','J']; 89 | 90 | values.forEach(function (value) { 91 | bucket.push('event-type', value, now); 92 | }); 93 | 94 | bucket.once('data', function (events) { 95 | assert.isArray(events); 96 | assert.sameMembers(events.map(function (event) { 97 | return event.value; 98 | }), values, 'Events in order'); 99 | 100 | count += events.length; 101 | }) 102 | .once('drain', function () { 103 | assert.strictEqual(count, values.length, values.length + ' events were sent'); 104 | done(); 105 | }) 106 | .once('error', done); 107 | 108 | }); 109 | 110 | does.it('auto-generate timestamp', function (done) { 111 | 112 | var count = 0; 113 | bucket.push('event-type', 'event-value'); 114 | bucket.once('data', function (events) { 115 | assert.isArray(events); 116 | assert.strictEqual(events[0].epoch, now, 'Event has expected "epoch"'); 117 | count += events.length; 118 | }) 119 | .once('drain', function () { 120 | assert.strictEqual(count, 1, 'One event was sent'); 121 | done(); 122 | }) 123 | .once('error', done); 124 | 125 | }); 126 | 127 | does.it('handles API overflow'); 128 | 129 | does.it('handles API error'); 130 | 131 | does.it('buffers when offline'); 132 | 133 | does.it('buffers until bucket exists'); 134 | 135 | does.it('flushes on process exit'); 136 | 137 | 138 | }); 139 | -------------------------------------------------------------------------------- /test/bucket.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var does = require('mocha'), 4 | mockery = require('mockery'), 5 | assert = require('chai').assert; 6 | 7 | 8 | does.describe('Buckets', function () { 9 | 10 | var isBucket; 11 | 12 | // Set Up 13 | 14 | does.before(function () { 15 | 16 | mockery.enable({ 17 | warnOnReplace: true, 18 | warnOnUnregistered: true, 19 | useCleanCache: true 20 | }); 21 | 22 | mockery.registerAllowable('../bucket'); 23 | mockery.registerAllowable('events'); 24 | mockery.registerAllowable('./epoch-clock'); 25 | mockery.registerSubstitute('./https-request', './test/https-request.mock'); 26 | 27 | isBucket = require('../bucket'); 28 | }); 29 | 30 | // Tear Down 31 | 32 | does.after(function () { 33 | mockery.deregisterAll(); 34 | mockery.disable(); 35 | }); 36 | 37 | // Tests 38 | 39 | does.it('creates a new bucket', function (done) { 40 | 41 | var newId = 'new-id', 42 | bucket = isBucket(newId, 'valid-key'); 43 | 44 | assert.instanceOf(bucket, isBucket.Bucket, 'create a local bucket'); 45 | assert.strictEqual(bucket.id, newId, 'bucket ID matches constructor argument'); 46 | assert.strictEqual(bucket.name, newId, 'Used the bucket ID when a bucket name is not available'); 47 | 48 | bucket.once('ready', function (isNew) { 49 | assert.strictEqual(isNew, true, 'created a new remote bucket'); 50 | done(); 51 | }) 52 | .once('error', done); 53 | 54 | }); 55 | 56 | does.it('set name of a new bucket', function (done) { 57 | 58 | var newId = 'new-id', 59 | newName = 'new-name', 60 | bucket = isBucket({ id: newId, name: newName }, 'valid-key'); 61 | 62 | assert.strictEqual(bucket.id, newId, 'bucket ID matches constructor argument'); 63 | assert.strictEqual(bucket.name, newName, 'bucket name matches constructor argument'); 64 | 65 | bucket.once('ready', function (isNew) { 66 | assert.strictEqual(isNew, true, 'created a new remote bucket'); 67 | done(); 68 | }) 69 | .once('error', done); 70 | 71 | }); 72 | 73 | does.it('use to an existing a bucket', function (done) { 74 | 75 | isBucket('existing-id', 'valid-key') 76 | .once('ready', function (isNew) { 77 | assert.strictEqual(isNew, false, 'used an existing bucket'); 78 | done(); 79 | }) 80 | .once('error', done); 81 | 82 | }); 83 | 84 | does.it('use access key from "IS_API_ACCESS_KEY" env var', function (done) { 85 | 86 | process.env.IS_API_ACCESS_KEY = 'env-var-key'; 87 | 88 | function cleanUp(err) { 89 | delete process.env.IS_API_ACCESS_KEY; 90 | done(err); 91 | } 92 | 93 | isBucket('existing-id') 94 | .once('ready', function () { 95 | // without an env var key, an Unauthenticated error should occur 96 | cleanUp(null); 97 | }) 98 | .once('error', cleanUp); 99 | 100 | }); 101 | 102 | does.it('handles missing access key', function (done) { 103 | 104 | isBucket('existing-id') 105 | .once('ready', function () { 106 | done(new Error('Failure Expected')); 107 | }) 108 | .once('error', function (err) { 109 | assert.ok(err, 'error provided'); 110 | assert.strictEqual(err.message, 'Unauthorized', 'Unauthorized error message'); 111 | done(); 112 | }); 113 | 114 | }); 115 | 116 | does.it('handles invalid access key', function (done) { 117 | 118 | isBucket('existing-id', 'invalid-key') 119 | .once('ready', function () { 120 | done(new Error('Failure Expected')); 121 | }) 122 | .once('error', function (err) { 123 | assert.ok(err, 'error provided'); 124 | assert.strictEqual(err.message, 'Unauthorized', 'Expected unauthorized error'); 125 | done(); 126 | }); 127 | 128 | }); 129 | 130 | does.it('handles api error', function (done) { 131 | 132 | isBucket('existing-id', 'error-key') 133 | .once('ready', function () { 134 | done(new Error('Failure Expected')); 135 | }) 136 | .once('error', function (err) { 137 | assert.ok(err, 'error provided'); 138 | assert.strictEqual(err.message, 'API Response Status 503', 'Expected unauthorized error'); 139 | done(); 140 | }); 141 | 142 | }); 143 | 144 | }); 145 | --------------------------------------------------------------------------------