From ae3e8f9546b8d08845583cad1c5797fc25d88eb5 Mon Sep 17 00:00:00 2001 From: Aaron Chambers Date: Tue, 21 Apr 2015 13:48:18 +0100 Subject: [PATCH] Now we can upload `index` to Redis. As yet we don't have activation or listing of recent uploads. Only uploading --- circle.yml | 10 +++ index.js | 74 +++++++++++++++- lib/redis.js | 56 ++++++++++++ package.json | 22 +++-- tests/runner.js | 27 ++++++ tests/unit/index-nodetest.js | 60 +++++++++++++ tests/unit/lib/redis-nodetest.js | 148 +++++++++++++++++++++++++++++++ 7 files changed, 390 insertions(+), 7 deletions(-) create mode 100644 circle.yml create mode 100644 lib/redis.js create mode 100644 tests/runner.js create mode 100644 tests/unit/index-nodetest.js create mode 100644 tests/unit/lib/redis-nodetest.js diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..f76ae3f --- /dev/null +++ b/circle.yml @@ -0,0 +1,10 @@ +machine: + node: + version: 0.12.0 + +dependencies: + pre: + - npm install -g bower + override: + - npm i + - bower i diff --git a/index.js b/index.js index fc23989..dec0646 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,78 @@ /* jshint node: true */ 'use strict'; +var fs = require('fs'); +var denodeify = require('rsvp').denodeify; +var path = require('path'); +var chalk = require('chalk'); + +var blue = chalk.blue; +var red = chalk.red; + +var Promise = require('ember-cli/lib/ext/promise'); + +var readFile = denodeify(fs.readFile); + module.exports = { - name: 'ember-cli-deploy-redis' + name: 'ember-cli-deploy-redis', + + createDeployPlugin: function(options) { + var Redis = require('./lib/redis'); + + function _readFileContents(path) { + return readFile(path) + .then(function(buffer) { + return buffer.toString(); + }); + } + + function _beginMessage(ui, indexPath) { + var filename = path.basename(indexPath); + + ui.write(blue('| ')); + ui.write(blue('- Uploading `' + filename + '`\n')); + + return Promise.resolve(); + } + + function _successMessage(ui, key) { + ui.write(blue('| ')); + ui.write(blue('- Uploaded with key `' + key + '`\n')); + + return Promise.resolve(key); + } + + function _errorMessage(ui, error) { + ui.write(blue('| ')); + ui.write(red('- ' + error + '`\n')); + + return Promise.reject(error); + } + + return { + name: options.name, + + upload: function(context) { + var deployment = context.deployment; + var ui = deployment.ui; + var config = deployment.config[this.name] || {}; + var redis = context.redisClient || new Redis(config); + + var projectName = deployment.project.name(); + var tag = context.tag; + var key = projectName + ':index'; + + var indexPath = context.indexPath; + + return _beginMessage(ui, indexPath) + .then(_readFileContents.bind(this, indexPath)) + .then(redis.upload.bind(redis, key, tag)) + .then(_successMessage.bind(this, ui)) + .then(function(key) { + return { redisKey: key } + }) + .catch(_errorMessage.bind(this, ui)); + } + }; + } }; diff --git a/lib/redis.js b/lib/redis.js new file mode 100644 index 0000000..74592c9 --- /dev/null +++ b/lib/redis.js @@ -0,0 +1,56 @@ +var CoreObject = require('core-object'); +var Promise = require('ember-cli/lib/ext/promise'); + +module.exports = CoreObject.extend({ + init: function(options) { + this._client = options.redisClient || require('then-redis').createClient(options); + + this._maxNumberOfRecentUploads = 10; + }, + + upload: function(/*key, tag, value*/) { + var args = Array.prototype.slice.call(arguments); + + var key = args.shift(); + var value = args.pop(); + var tag = args[0] || 'default'; + var redisKey = key + ':' + tag; + + var maxEntries = this._maxNumberOfRecentUploads; + + return Promise.resolve() + .then(this._uploadIfKeyDoesNotExist.bind(this, redisKey, value)) + .then(this._updateRecentUploadsList.bind(this, key, tag)) + .then(this._trimRecentUploadsList.bind(this, key, maxEntries)) + .then(function() { + return redisKey; + }); + }, + + _uploadIfKeyDoesNotExist: function(redisKey, value) { + var client = this._client; + + return Promise.resolve() + .then(function() { + return client.get(redisKey); + }) + .then(function(value) { + if (value) { + return Promise.reject('Value already exists for key: ' + redisKey); + } + }) + .then(function() { + return client.set(redisKey, value); + }) + }, + + _updateRecentUploadsList: function(key, tag) { + var client = this._client; + return client.lpush(key, tag); + }, + + _trimRecentUploadsList: function(key, maxEntries) { + var client = this._client; + return client.ltrim(key, 0, maxEntries - 1); + } +}); diff --git a/package.json b/package.json index d489b02..c133d7b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "start": "ember server", "build": "ember build", - "test": "ember try:testall" + "test": "node tests/runner.js" }, "repository": "", "engines": { @@ -19,6 +19,8 @@ "license": "MIT", "devDependencies": { "broccoli-asset-rev": "^2.0.2", + "chai": "^2.2.0", + "chai-as-promised": "^5.0.0", "ember-cli": "0.2.3", "ember-cli-app-version": "0.3.3", "ember-cli-content-security-policy": "0.4.0", @@ -29,17 +31,25 @@ "ember-cli-qunit": "0.3.10", "ember-cli-uglify": "1.0.1", "ember-data": "1.0.0-beta.16.1", - "ember-export-application-global": "^1.0.2", "ember-disable-prototype-extensions": "^1.0.0", - "ember-try": "0.0.4" + "ember-export-application-global": "^1.0.2", + "ember-try": "0.0.4", + "glob": "^5.0.5", + "mocha": "^2.2.4" }, "keywords": [ - "ember-addon" + "ember-addon", + "ember-cli-deploy-plugin" ], "dependencies": { - "ember-cli-babel": "^5.0.0" + "chalk": "^1.0.0", + "core-object": "^1.1.0", + "ember-cli-babel": "^5.0.0", + "redis": "^0.12.1", + "rsvp": "^3.0.18", + "then-redis": "^1.3.0" }, "ember-addon": { "configPath": "tests/dummy/config" } -} \ No newline at end of file +} diff --git a/tests/runner.js b/tests/runner.js new file mode 100644 index 0000000..1ffb333 --- /dev/null +++ b/tests/runner.js @@ -0,0 +1,27 @@ +'use strict'; + +var glob = require('glob'); +var Mocha = require('mocha'); + +var mocha = new Mocha({ + reporter: 'spec' +}); + +var arg = process.argv[2]; +var root = 'tests/'; + +function addFiles(mocha, files) { + glob.sync(root + files).forEach(mocha.addFile.bind(mocha)); +} + +addFiles(mocha, '/**/*-nodetest.js'); + +if (arg === 'all') { + addFiles(mocha, '/**/*-nodetest-slow.js'); +} + +mocha.run(function(failures) { + process.on('exit', function() { + process.exit(failures); + }); +}); diff --git a/tests/unit/index-nodetest.js b/tests/unit/index-nodetest.js new file mode 100644 index 0000000..3459c1e --- /dev/null +++ b/tests/unit/index-nodetest.js @@ -0,0 +1,60 @@ +'use strict'; + +var Promise = require('ember-cli/lib/ext/promise'); + +var assert = require('ember-cli/tests/helpers/assert'); + +describe('redis plugin', function() { + var subject; + + before(function() { + subject = require('../../index'); + }); + + it('has a name', function() { + var result = subject.createDeployPlugin({ + name: 'test-plugin' + }); + + assert.equal(result.name, 'test-plugin'); + }); + + it('implements the correct hooks', function() { + var result = subject.createDeployPlugin({ + name: 'test-plugin' + }); + + assert.equal(typeof result.upload, 'function'); + }); + + describe('upload hook', function() { + it('uploads the index to redis', function() { + var plugin = subject.createDeployPlugin({ + name: 'test-plugin' + }); + + var context = { + redisClient: { + upload: function() { + return Promise.resolve('redis-key'); + } + }, + tag: 'some-tag', + indexPath: 'tests/index.html', + deployment: { + ui: { write: function() {} }, + project: { name: function() { return 'test-project'; } }, + config: {} + } + }; + + return assert.isFulfilled(plugin.upload.call(plugin, context)) + .then(function(result) { + assert.deepEqual(result, { redisKey: 'redis-key' }); + }); + }); + + it('returns the uploaded key', function() { + }); + }); +}); diff --git a/tests/unit/lib/redis-nodetest.js b/tests/unit/lib/redis-nodetest.js new file mode 100644 index 0000000..8db2e20 --- /dev/null +++ b/tests/unit/lib/redis-nodetest.js @@ -0,0 +1,148 @@ +'use strict'; + +var Promise = require('ember-cli/lib/ext/promise'); +var assert = require('ember-cli/tests/helpers/assert'); + +describe('redis', function() { + var Redis; + + before(function() { + Redis = require('../../../lib/redis'); + }); + + describe('#upload', function() { + it('rejects if the key already exists in redis', function() { + var redis = new Redis({ + redisClient: { + get: function(key) { + return Promise.resolve('some-other-value'); + }, + set: function() { }, + lpush: function() { }, + ltrim: function() { } + } + }); + + var promise = redis.upload('key', 'value'); + return assert.isRejected(promise, /^Value already exists for key: key:default$/); + }); + + it('uploads the contents if the key does not already exist', function() { + var fileUploaded = false; + + var redis = new Redis({ + redisClient: { + get: function(key) { + return Promise.resolve(null); + }, + set: function(key, value) { + fileUploaded = true; + }, + lpush: function() { }, + ltrim: function() { } + } + }); + + var promise = redis.upload('key', 'value'); + return assert.isFulfilled(promise) + .then(function() { + assert.ok(fileUploaded); + }); + }); + + it('updates the list of recent uploads once upload is successful', function() { + var recentUploads = []; + + var redis = new Redis({ + redisClient: { + get: function(key) { + return Promise.resolve(null); + }, + set: function(key, value) { + }, + lpush: function(key, tag) { + recentUploads.push(key + tag); + }, + ltrim: function() { } + } + }); + + var promise = redis.upload('key', 'value'); + return assert.isFulfilled(promise) + .then(function() { + assert.equal(recentUploads.length, 1); + assert.equal(recentUploads[0], 'keydefault'); + }); + }); + + it('trims the list of recent uploads', function() { + var recentUploads = ['a', 'b', 'c']; + + var redis = new Redis({ + redisClient: { + get: function(key) { + return Promise.resolve(null); + }, + set: function(key, value) { + }, + lpush: function(key, tag) { + recentUploads.push(key + tag); + }, + ltrim: function() { + recentUploads.pop(); + } + } + }); + + var promise = redis.upload('key', 'value'); + return assert.isFulfilled(promise) + .then(function() { + assert.equal(recentUploads.length, 3); + }); + }); + + describe('generating the redis key', function() { + it('will use just the default tag if the tag is not provided', function() { + var redisKey = null; + var redis = new Redis({ + redisClient: { + get: function(key) { + redisKey = key; + return Promise.resolve('some-other-value'); + }, + set: function() { }, + lpush: function() { }, + ltrim: function() { } + } + }); + + var promise = redis.upload('key', 'value'); + return assert.isRejected(promise) + .then(function() { + assert.equal(redisKey, 'key:default'); + }) + }); + + it('will use the key and the tag if the tag is provided', function() { + var redisKey = null; + var redis = new Redis({ + redisClient: { + get: function(key) { + redisKey = key; + return Promise.resolve('some-other-value'); + }, + set: function() { }, + lpush: function() { }, + ltrim: function() { } + } + }); + + var promise = redis.upload('key', 'tag', 'value'); + return assert.isRejected(promise) + .then(function() { + assert.equal(redisKey, 'key:tag'); + }) + }); + }); + }); +});