diff --git a/README.md b/README.md index 60f2b6590c6..6aaabb6c49d 100644 --- a/README.md +++ b/README.md @@ -242,18 +242,18 @@ ds.allocateIds(['ns-test', 'Company', null], 100, function(err, keys) { Datastore has support for transactions. Transactions allow you to perform multiple operations and commiting your changes atomically. -`runInTransaction` is a utility method to work with transactions. +`transaction` is a utility method to work with transactions. ~~~~ js -ds.runInTransaction(function(t, done) { - // perform actions via t. - t.get(key, function(err, obj) { +ds.transaction(function(transaction, done) { + // call datastore methods as usual + // when you're done, call done + transaction.get(key, function(err, entity) { if (err) { - t.rollback(done); + transaction.rollback(done); return; } - // do any other operation with obj. - // when you're done, call done. + // do any other operations with entity. done(); }); }, function(err) { @@ -262,18 +262,11 @@ ds.runInTransaction(function(t, done) { }); ~~~~ -The transaction will be auto committed if it's not rollbacked or -commited when done is called. Transactions support the following -CRUD and transaction related operations. - -* t.get(key, callback) -* t.getAll(keys, callback) -* t.save(key, obj, callback) -* t.saveAll(keys, objs, callback) -* t.del(key, callback) -* t.delAll(keys, callback) -* t.rollback(callback) -* t.commit(callback) +* transaction.get([key], callback); +* transaction.save([{ key: '', data: {} }], callback); +* transaction.delete([key], callback); +* transaction.rollback(callback); +* transaction.commit(callback); ### Google Cloud Storage diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js new file mode 100644 index 00000000000..163ca353343 --- /dev/null +++ b/lib/datastore/dataset.js @@ -0,0 +1,135 @@ +/** + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var conn = require('../common/connection.js'); +var entity = require('./entity.js'); +var Transaction = require('./transaction.js'); +var Query = require('./query.js'); +var util = require('../common/util.js'); + +var SCOPES = [ + 'https://www.googleapis.com/auth/datastore', + 'https://www.googleapis.com/auth/userinfo.email' +]; + +/** + * Creates a new dataset with the provided options. + * @param {object} opts Dataset identifier options. + * @param {string} opts.id Dataset ID, this is your project ID + * from Google Developers Console. + * @param {string} opts.keyFilename Path to the JSON key file downloaded from + * Google Developers Console. + */ +function Dataset(opts) { + opts = opts || {}; + var id = opts.projectId || ''; + + if (id.indexOf('s~') === -1 && id.indexOf('e~') === -1) { + id = 's~' + id; + } + + this.connection = new conn.Connection({ + keyFilename: opts.keyFilename, + scopes: SCOPES + }); + this.id = id; + this.transaction = this.createTransaction(); +} + +/** + * Creates a query from the current dataset to query the specified + * kinds. + * + * Example usage: + * ds.createQuery(['Lion', 'Chimp']) + * ds.createQuery('zoo', ['Lion', 'Chimp']) + * + * @param {string=} ns Optional namespace to query entities from. + * @param {Array} kinds A list of kinds to query. + * @return {Query} + */ +Dataset.prototype.createQuery = function(ns, kinds) { + if (!kinds) { + kinds = ns; + ns = ''; + } + kinds = util.arrayize(kinds); + return new Query(ns, kinds); +}; + +/** + * Gets the object identified with the provided key. + * @param {Key} key A non-incomplete key. + * @param {Function} callback The callback function. + */ +Dataset.prototype.get = function(key, callback) { + this.transaction.get(key, callback); +}; + +Dataset.prototype.save = function(key, obj, callback) { + this.transaction.save(key, obj, callback); +}; + +Dataset.prototype.delete = function(key, callback) { + this.transaction.delete(key, callback); +}; + +Dataset.prototype.runQuery = function(q, callback) { + this.transaction.runQuery(q, callback); +}; + +Dataset.prototype.runInTransaction = function(fn, callback) { + var newTransaction = this.createTransaction(); + newTransaction.begin(function(err) { + if (err) { + return callback(err); + } + fn(newTransaction, newTransaction.finalize.bind(newTransaction, callback)); + }); +}; + +Dataset.prototype.allocateIds = function(incompleteKey, n, callback) { + if (entity.isKeyComplete(incompleteKey)) { + throw new Error('An incomplete key should be provided.'); + } + var incompleteKeys = []; + for (var i = 0; i < n; i++) { + incompleteKeys.push(entity.keyToKeyProto(this.id, incompleteKey)); + } + this.transaction.makeReq( + 'allocateIds', { keys: incompleteKeys }, function(err, resp) { + if (err) { + return callback(err); + } + var keys = []; + resp.keys.forEach(function(k) { + keys.push(entity.keyFromKeyProto(k)); + }); + callback(null ,keys); + }); +}; + +Dataset.prototype.createTransaction = function() { + return new Transaction(this.connection, this.id); +}; + +/** + * Exports Dataset. + * @type {Dataset} + */ +module.exports = Dataset; diff --git a/lib/datastore/index.js b/lib/datastore/index.js index f88d7b2f273..3eccac9a204 100644 --- a/lib/datastore/index.js +++ b/lib/datastore/index.js @@ -16,349 +16,14 @@ 'use strict'; -var conn = require('../common/connection.js'); -var entity = require('./entity.js'); -var Query = require('./query.js').Query; -var util = require('../common/util.js'); +var entity = require('./entity'); -var DATASTORE_BASE_URL = - 'https://www.googleapis.com/datastore/v1beta2/datasets'; -var MODE_NON_TRANSACTIONAL = 'NON_TRANSACTIONAL'; -var MODE_TRANSACTIONAL = 'TRANSACTIONAL'; -var SCOPES = [ - 'https://www.googleapis.com/auth/datastore', - 'https://www.googleapis.com/auth/userinfo.email' -]; - -/** - * Transaction represents a Datastore transaction. - */ -function Transaction(conn, datasetId) { - this.conn = conn; - this.datasetId = datasetId; - // the default transaction has no id. - // if id is not set, run operations non-transactional. - this.id = null; - this.isFinalized = false; -} - -/** - * Begins starts a remote transaction and identifies the current - * transaction instance with the remote transaction's ID. - */ -Transaction.prototype.begin = function(callback) { - callback = callback || util.noop; - var that = this; - this.makeReq('beginTransaction', null, function(err, resp) { - if (!err) { - that.id = resp.transaction; - } - callback(err); - }); -}; - -/** - * Rollback rollsback a transaction remotely and finalizes - * the current transaction instance. - */ -Transaction.prototype.rollback = function(callback) { - callback = callback || util.noop; - var that = this; - var req = { transaction: this.id }; - this.makeReq('rollback', req, function(err) { - if (!err) { - that.isFinalized = true; - } - callback(err); - }); -}; - -/** - * Commit commits the remote transaction and finalizes the - * current transaction instance. - */ -Transaction.prototype.commit = function(callback) { - callback = callback || util.noop; - var that = this; - var req = { transaction: this.id }; - this.makeReq('commit', req, function(err) { - if (!err) { - that.isFinalized = true; - } - callback(err); - }); -}; - -/** - * Finalize commits a transaction if it's not finalized yet. - */ -Transaction.prototype.finalize = function(callback) { - if (!this.isFinalized) { - return this.commit(callback); - } - callback(); -}; - -/** - * Get retrieves the objects identified with the specified key(s) in the - * current transaction. - * @param {Array} keys - * @param {Function} callback - */ -Transaction.prototype.get = function(keys, callback) { - var isMultipleRequest = Array.isArray(keys[0]); - keys = isMultipleRequest ? keys : [keys]; - callback = callback || util.noop; - var req = { - keys: keys.map(entity.keyToKeyProto.bind(null, this.id)) - }; - if (this.id) { - req.transaction = this.id; - } - this.makeReq( - 'lookup', req, function(err, resp) { - if (err) { - return callback(err); - } - var response = entity.formatArray(resp.found); - callback(null, isMultipleRequest ? response : response[0]); - }); -}; - -/** - * Inserts or upates the specified object(s) in the current transaction. - * If a key is incomplete, its associated object is inserted and - * generated identifier is returned. - * @param {Array} entities - * @param {Function} callback - */ -Transaction.prototype.save = function(entities, callback) { - var isMultipleRequest = Array.isArray(entities); - entities = isMultipleRequest ? entities : [entities]; - var insertIndexes = []; - var keys = entities.map(function(entityObject) { - return entityObject.key; - }); - var req = { - mode: MODE_NON_TRANSACTIONAL, - mutation: entities.reduce(function(acc, entityObject, index) { - var ent = entity.entityToEntityProto(entityObject.data); - ent.key = entity.keyToKeyProto(this.datasetId, entityObject.key); - if (entity.isKeyComplete(entityObject.key)) { - acc.upsert.push(ent); - } else { - insertIndexes.push(index); - acc.insertAutoId.push(ent); - } - return acc; - }.bind(this), { upsert: [], insertAutoId: [] }) - }; - if (this.id) { - req.transaction = this.id; - req.mode = MODE_TRANSACTIONAL; - } - this.makeReq( - 'commit', req, function(err, resp) { - if (err || !resp) { - return callback(err); - } - (resp.mutationResult.insertAutoIdKeys || []).forEach(function(key, index) { - keys[insertIndexes[index]] = entity.keyFromKeyProto(key); - }); - callback(null, isMultipleRequest ? keys : keys[0]); - }); -}; - -/** - * Deletes all entities identified with the specified list of key(s) - * in the current transaction. - * @param {Array} keys - * @param {Function} callback - */ -Transaction.prototype.delete = function(keys, callback) { - var isMultipleRequest = Array.isArray(keys[0]); - keys = isMultipleRequest ? keys : [keys]; - callback = callback || util.noop; - var req = { - mode: MODE_NON_TRANSACTIONAL, - mutation: { - delete: keys.map(entity.keyToKeyProto.bind(null, this.id)) - } - }; - if (this.id) { - req.transaction = this.id; - req.mode = MODE_TRANSACTIONAL; - } - this.makeReq('commit', req, callback); -}; - -/** - * Runs a query in the current transaction. If more results are - * available, a query to retrieve the next page is provided to - * the callback function. Example: - * - * t.runQuery(q, function(err, entities, nextQuery) { - * if (err) return; - * // if next page is available, retrieve more results - * if (nextQuery) { - * t.runQuery(nextQuery, ...); - * } - * }); - * @param {Query} q - * @param {Function} callback - */ -Transaction.prototype.runQuery = function(q, callback) { - callback = callback || util.noop; - var req = { - readOptions: { - transaction: this.id - }, - query: entity.queryToQueryProto(q) - }; - if (q.namespace) { - req.partitionId = { - namespace: q.namespace - }; - } - this.makeReq('runQuery', req, function(err, resp) { - if (err || !resp.batch || !resp.batch.entityResults) { - return callback(err); - } - var nextQuery = null; - if (resp.batch.endCursor && resp.batch.endCursor !== q.startVal) { - nextQuery = q.start(resp.batch.endCursor).offset(0); - } - callback(null, entity.formatArray(resp.batch.entityResults), nextQuery); - }); -}; - -Transaction.prototype.mapQuery = function() { - throw new Error('not yet implemented'); -}; - -/** - * Makes a request to the API endpoint. - */ -Transaction.prototype.makeReq = function(method, req, callback) { - // TODO(jbd): Switch to protobuf API. - // Always pass a value for json to automagically get headers/response parsing. - req = req || {}; - this.conn.req({ - method: 'POST', - uri: DATASTORE_BASE_URL + '/' + this.datasetId + '/' + method, - json: req - }, function(err, res, body) { - util.handleResp(err, res, body, callback); - }); -}; - -/** - * Creates a new dataset with the provided options. - * @param {object} opts Dataset identifier options. - * @param {string} opts.id Dataset ID, this is your project ID - * from Google Developers Console. - * @param {string} opts.keyFilename Path to the JSON key file downloaded from - * Google Developers Console. - */ -function Dataset(opts) { - opts = opts || {}; - var id = opts.projectId || ''; - - if (id.indexOf('s~') === -1 && id.indexOf('e~') === -1) { - id = 's~' + id; - } - - this.id = id; - this.transaction = new Transaction(new conn.Connection({ - keyFilename: opts.keyFilename, - scopes: SCOPES - }), this.id); -} - -/** - * Creates a query from the current dataset to query the specified - * kinds. - * - * Example usage: - * ds.createQuery(['Lion', 'Chimp']) - * ds.createQuery('zoo', ['Lion', 'Chimp']) - * - * @param {string=} ns Optional namespace to query entities from. - * @param {Array} kinds A list of kinds to query. - * @return {Query} - */ -Dataset.prototype.createQuery = function(ns, kinds) { - if (!kinds) { - kinds = ns; - ns = ''; - } - kinds = util.arrayize(kinds); - return new Query(ns, kinds); -}; - -/** - * Gets the object identified with the provided key. - * @param {Key} key A non-incomplete key. - * @param {Function} callback The callback function. - */ -Dataset.prototype.get = function(key, callback) { - this.transaction.get(key, callback); -}; - -Dataset.prototype.save = function(key, obj, callback) { - this.transaction.save(key, obj, callback); -}; - -Dataset.prototype.delete = function(key, callback) { - this.transaction.delete(key, callback); -}; - -Dataset.prototype.runQuery = function(q, callback) { - this.transaction.runQuery(q, callback); -}; - -Dataset.prototype.runInTransaction = function(fn, callback) { - var t = new Transaction(this.transaction.conn, this.id); - var done = function() { - t.finalize(callback); - }; - t.begin(function(err) { - if (err) { - return callback(err); - } - fn(t, done); - }); -}; - -Dataset.prototype.allocateIds = function(incompleteKey, n, callback) { - if (entity.isKeyComplete(incompleteKey)) { - throw new Error('An incomplete key should be provided.'); +module.exports = { + Dataset: require('./dataset'), + Int: function(value) { + return new entity.Int(value); + }, + Double: function(value) { + return new entity.Double(value); } - var incompleteKeys = []; - for (var i = 0; i < n; i++) { - incompleteKeys.push(entity.keyToKeyProto(this.id, incompleteKey)); - } - this.transaction.makeReq( - 'allocateIds', { keys: incompleteKeys }, function(err, resp) { - if (err) { - return callback(err); - } - var keys = []; - resp.keys.forEach(function(k) { - keys.push(entity.keyFromKeyProto(k)); - }); - callback(null ,keys); - }); }; - -/** - * Exports Dataset. - * @type {Dataset} - */ -module.exports.Dataset = Dataset; - -/** - * Exports Transaction. - * @type {Transaction} - */ -module.exports.Transaction = Transaction; diff --git a/lib/datastore/query.js b/lib/datastore/query.js index 0743bc6ede3..53bf1382abc 100644 --- a/lib/datastore/query.js +++ b/lib/datastore/query.js @@ -104,4 +104,4 @@ Query.prototype.offset = function(n) { /** * Exports Query. */ -module.exports.Query = Query; +module.exports = Query; diff --git a/lib/datastore/transaction.js b/lib/datastore/transaction.js new file mode 100644 index 00000000000..54dea3c0d18 --- /dev/null +++ b/lib/datastore/transaction.js @@ -0,0 +1,253 @@ +/** + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var entity = require('./entity.js'); +var util = require('../common/util.js'); + +var DATASTORE_BASE_URL = + 'https://www.googleapis.com/datastore/v1beta2/datasets'; +var MODE_NON_TRANSACTIONAL = 'NON_TRANSACTIONAL'; +var MODE_TRANSACTIONAL = 'TRANSACTIONAL'; + +/** + * Transaction represents a Datastore transaction. + */ +function Transaction(conn, datasetId) { + this.conn = conn; + this.datasetId = datasetId; + // the default transaction has no id. + // if id is not set, run operations non-transactional. + this.id = null; + this.isFinalized = false; +} + +/** + * Begins starts a remote transaction and identifies the current + * transaction instance with the remote transaction's ID. + */ +Transaction.prototype.begin = function(callback) { + callback = callback || util.noop; + var that = this; + this.makeReq('beginTransaction', null, function(err, resp) { + if (!err) { + that.id = resp.transaction; + } + callback(err); + }); +}; + +/** + * Rollback rollsback a transaction remotely and finalizes + * the current transaction instance. + */ +Transaction.prototype.rollback = function(callback) { + callback = callback || util.noop; + var that = this; + var req = { transaction: this.id }; + this.makeReq('rollback', req, function(err) { + if (!err) { + that.isFinalized = true; + } + callback(err); + }); +}; + +/** + * Commit commits the remote transaction and finalizes the + * current transaction instance. + */ +Transaction.prototype.commit = function(callback) { + callback = callback || util.noop; + var that = this; + var req = { transaction: this.id }; + this.makeReq('commit', req, function(err) { + if (!err) { + that.isFinalized = true; + } + callback(err); + }); +}; + +/** + * Finalize commits a transaction if it's not finalized yet. + */ +Transaction.prototype.finalize = function(callback) { + if (!this.isFinalized) { + return this.commit(callback); + } + callback(); +}; + +/** + * Get retrieves the objects identified with the specified key(s) in the + * current transaction. + * @param {Array} keys + * @param {Function} callback + */ +Transaction.prototype.get = function(keys, callback) { + var isMultipleRequest = Array.isArray(keys[0]); + keys = isMultipleRequest ? keys : [keys]; + callback = callback || util.noop; + var req = { + keys: keys.map(entity.keyToKeyProto.bind(null, this.id)) + }; + if (this.id) { + req.transaction = this.id; + } + this.makeReq( + 'lookup', req, function(err, resp) { + if (err) { + return callback(err); + } + var response = entity.formatArray(resp.found); + callback(null, isMultipleRequest ? response : response[0]); + }); +}; + +/** + * Inserts or upates the specified object(s) in the current transaction. + * If a key is incomplete, its associated object is inserted and + * generated identifier is returned. + * @param {Array} entities + * @param {Function} callback + */ +Transaction.prototype.save = function(entities, callback) { + var isMultipleRequest = Array.isArray(entities); + entities = isMultipleRequest ? entities : [entities]; + var insertIndexes = []; + var keys = entities.map(function(entityObject) { + return entityObject.key; + }); + var req = { + mode: MODE_NON_TRANSACTIONAL, + mutation: entities.reduce(function(acc, entityObject, index) { + var ent = entity.entityToEntityProto(entityObject.data); + ent.key = entity.keyToKeyProto(this.datasetId, entityObject.key); + if (entity.isKeyComplete(entityObject.key)) { + acc.upsert.push(ent); + } else { + insertIndexes.push(index); + acc.insertAutoId.push(ent); + } + return acc; + }.bind(this), { upsert: [], insertAutoId: [] }) + }; + if (this.id) { + req.transaction = this.id; + req.mode = MODE_TRANSACTIONAL; + } + this.makeReq( + 'commit', req, function(err, resp) { + if (err || !resp) { + return callback(err); + } + (resp.mutationResult.insertAutoIdKeys || []).forEach(function(key, index) { + keys[insertIndexes[index]] = entity.keyFromKeyProto(key); + }); + callback(null, isMultipleRequest ? keys : keys[0]); + }); +}; + +/** + * Deletes all entities identified with the specified list of key(s) + * in the current transaction. + * @param {Array} keys + * @param {Function} callback + */ +Transaction.prototype.delete = function(keys, callback) { + var isMultipleRequest = Array.isArray(keys[0]); + keys = isMultipleRequest ? keys : [keys]; + callback = callback || util.noop; + var req = { + mode: MODE_NON_TRANSACTIONAL, + mutation: { + delete: keys.map(entity.keyToKeyProto.bind(null, this.id)) + } + }; + if (this.id) { + req.transaction = this.id; + req.mode = MODE_TRANSACTIONAL; + } + this.makeReq('commit', req, callback); +}; + +/** + * Runs a query in the current transaction. If more results are + * available, a query to retrieve the next page is provided to + * the callback function. Example: + * + * t.runQuery(q, function(err, entities, nextQuery) { + * if (err) return; + * // if next page is available, retrieve more results + * if (nextQuery) { + * t.runQuery(nextQuery, ...); + * } + * }); + * @param {Query} q + * @param {Function} callback + */ +Transaction.prototype.runQuery = function(q, callback) { + callback = callback || util.noop; + var req = { + readOptions: { + transaction: this.id + }, + query: entity.queryToQueryProto(q) + }; + if (q.namespace) { + req.partitionId = { + namespace: q.namespace + }; + } + this.makeReq('runQuery', req, function(err, resp) { + if (err || !resp.batch || !resp.batch.entityResults) { + return callback(err); + } + var nextQuery = null; + if (resp.batch.endCursor && resp.batch.endCursor !== q.startVal) { + nextQuery = q.start(resp.batch.endCursor).offset(0); + } + callback(null, entity.formatArray(resp.batch.entityResults), nextQuery); + }); +}; + +Transaction.prototype.mapQuery = function() { + throw new Error('not yet implemented'); +}; + +/** + * Makes a request to the API endpoint. + */ +Transaction.prototype.makeReq = function(method, req, callback) { + // TODO(jbd): Switch to protobuf API. + // Always pass a value for json to automagically get headers/response parsing. + req = req || {}; + this.conn.req({ + method: 'POST', + uri: DATASTORE_BASE_URL + '/' + this.datasetId + '/' + method, + json: req + }, function(err, res, body) { + util.handleResp(err, res, body, callback); + }); +}; + +/** + * Exports Transaction. + * @type {Transaction} + */ +module.exports = Transaction; diff --git a/lib/index.js b/lib/index.js index 21b15280549..32783a9066b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -16,19 +16,8 @@ 'use strict'; -var datastore = require('./datastore'); -var entity = require('./datastore/entity.js'); - -datastore.Int = function(val) { - return new entity.Int(val); -}; - -datastore.Double = function(val) { - return new entity.Double(val); -}; - module.exports = { - datastore: datastore, - storage: require('./storage'), - pubsub: require('./pubsub') + datastore: require('./datastore'), + pubsub: require('./pubsub'), + storage: require('./storage') }; diff --git a/package.json b/package.json index 3b39376371d..61fa736dcb1 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,13 @@ "istanbul": "^0.3.0", "jshint": "^2.5.2", "mocha": "^1.21.3", + "sandboxed-module": "^1.0.1", "tmp": "0.0.24" }, "scripts": { - "test": "jshint lib/**/*.js regression/*.js test/*.js && mocha --reporter spec", - "regression-test": "mocha --reporter spec --timeout 10000 regression/*", + "lint": "jshint lib/ regression/ test/", + "test": "mocha --recursive --reporter spec", + "regression-test": "mocha regression/ --reporter spec --timeout 10000", "cover": "istanbul cover mocha -- --timeout 10000 test/* regression/*" }, "license": "Apache 2" diff --git a/test/common.connection.js b/test/common/connection.js similarity index 97% rename from test/common.connection.js rename to test/common/connection.js index 48025cbc8c6..74b700505b8 100644 --- a/test/common.connection.js +++ b/test/common/connection.js @@ -20,10 +20,9 @@ var assert = require('assert'); var async = require('async'); -var conn = require('../lib/common/connection.js'); +var conn = require('../../lib/common/connection.js'); describe('Connection', function() { - var tokenNeverExpires = new conn.Token('token', new Date(3000, 0, 0)); var tokenExpired = new conn.Token('token', new Date(2011, 0, 0)); it('should fetch a new token if token expires', function(done) { @@ -86,5 +85,4 @@ describe('Connection', function() { }; c.req({ uri: 'https://someuri' }, function(){}); }); - }); diff --git a/test/common.util.js b/test/common/util.js similarity index 97% rename from test/common.util.js rename to test/common/util.js index 1b7a779ca1a..b1c2a8834c0 100644 --- a/test/common.util.js +++ b/test/common/util.js @@ -19,7 +19,7 @@ 'use strict'; var assert = require('assert'); -var util = require('../lib/common/util.js'); +var util = require('../../lib/common/util.js'); describe('extend', function() { it ('should return null for null input', function() { diff --git a/test/datastore.js b/test/datastore/dataset.js similarity index 84% rename from test/datastore.js rename to test/datastore/dataset.js index 9540b79f26f..4977a71b28a 100644 --- a/test/datastore.js +++ b/test/datastore/dataset.js @@ -19,58 +19,9 @@ 'use strict'; var assert = require('assert'); -var datastore = require('../lib').datastore; -var mockRespGet = require('./testdata/response_get.json'); - -describe('Transaction', function() { - it('should begin', function(done) { - var t = new datastore.Transaction(null, 'test'); - t.makeReq = function(method, proto, callback) { - assert.equal(method, 'beginTransaction'); - assert.equal(proto, null); - callback(null, 'some-id'); - }; - t.begin(done); - }); - - it('should rollback', function(done) { - var t = new datastore.Transaction(null, 'test'); - t.id = 'some-id'; - t.makeReq = function(method, proto, callback) { - assert.equal(method, 'rollback'); - assert.deepEqual(proto, { transaction: 'some-id' }); - callback(); - }; - t.rollback(function() { - assert.equal(t.isFinalized, true); - done(); - }); - }); - - it('should commit', function(done) { - var t = new datastore.Transaction(null, 'test'); - t.id = 'some-id'; - t.makeReq = function(method, proto, callback) { - assert.equal(method, 'commit'); - assert.deepEqual(proto, { transaction: 'some-id' }); - callback(); - }; - t.commit(function() { - assert.equal(t.isFinalized, true); - done(); - }); - }); - - it('should be committed if not rolled back', function(done) { - var t = new datastore.Transaction(null, 'test'); - t.isFinalized = false; - t.makeReq = function(method) { - assert.equal(method, 'commit'); - done(); - }; - t.finalize(); - }); -}); +var datastore = require('../../lib').datastore; +var mockRespGet = require('../testdata/response_get.json'); +var Transaction = require('../../lib/datastore/transaction.js'); describe('Dataset', function() { it('should append ~s if ~s or ~e are not presented', function(done) { @@ -202,6 +153,42 @@ describe('Dataset', function() { }); }); + describe('runInTransaction', function() { + var ds; + var transaction; + + beforeEach(function() { + ds = new datastore.Dataset({ projectId: 'test' }); + ds.createTransaction = function() { + transaction = new Transaction(); + transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'beginTransaction'); + callback(null, { transaction: '' }); + }; + return transaction; + }; + }); + + it('should begin transaction', function() { + ds.runInTransaction(function() {}, function() {}); + }); + + it('should return transaction object to the callback', function() { + ds.runInTransaction(function(transactionObject) { + assert.equal(transactionObject, transaction); + }, assert.ifError); + }); + + it('should commit the transaction when done', function() { + ds.runInTransaction(function(t, done) { + transaction.makeReq = function(method) { + assert.equal(method, 'commit'); + }; + done(); + }, assert.ifError); + }); + }); + describe('runQuery', function() { var ds; var query; diff --git a/test/datastore.entity.js b/test/datastore/entity.js similarity index 98% rename from test/datastore.entity.js rename to test/datastore/entity.js index 847d7f7eea9..324cc3dedb5 100644 --- a/test/datastore.entity.js +++ b/test/datastore/entity.js @@ -19,8 +19,8 @@ 'use strict'; var assert = require('assert'); -var entity = require('../lib/datastore/entity.js'); -var datastore = require('../lib/datastore'); +var entity = require('../../lib/datastore/entity.js'); +var datastore = require('../../lib/datastore'); var blogPostMetadata = { title: { kind: String, indexed: true }, diff --git a/test/datastore/index.js b/test/datastore/index.js new file mode 100644 index 00000000000..e479c3c13c3 --- /dev/null +++ b/test/datastore/index.js @@ -0,0 +1,56 @@ +/** + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*global describe, it */ + +'use strict'; + +var entity = { + Int: function(val) { + entity.intCalledWith = val; + }, + Double: function(val) { + entity.doubleCalledWith = val; + }, + intCalledWith: null, + doubleCalledWith: null +}; + +var assert = require('assert'); +var datastore = require('sandboxed-module') + .require('../../lib/datastore/index.js', { + requires: { + './entity': entity + } + }); + +describe('Datastore', function() { + it('should expose Dataset class', function() { + assert.equal(typeof datastore.Dataset, 'function'); + }); + + it('should expose Int builder', function() { + var anInt = 7; + datastore.Int(anInt); + assert.equal(entity.intCalledWith, anInt); + }); + + it('should expose Double builder', function() { + var aDouble = 7.0; + datastore.Double(aDouble); + assert.equal(entity.doubleCalledWith, aDouble); + }); +}); diff --git a/test/datastore.query.js b/test/datastore/query.js similarity index 95% rename from test/datastore.query.js rename to test/datastore/query.js index 651bbeffa79..913cd973050 100644 --- a/test/datastore.query.js +++ b/test/datastore/query.js @@ -19,12 +19,13 @@ 'use strict'; var assert = require('assert'); -var datastore = require('../lib/datastore'); -var entity = require('../lib/datastore/entity.js'); -var queryProto = require('./testdata/proto_query.json'); +var datastore = require('../../lib/datastore'); +var entity = require('../../lib/datastore/entity.js'); +var queryProto = require('../testdata/proto_query.json'); describe('Query', function() { var ds = new datastore.Dataset({ projectId: 'my-project-id' }); + it('should use default namespace if none is specified', function(done) { var q = ds.createQuery(['kind1']); assert.equal(q.namespace, ''); @@ -117,8 +118,7 @@ describe('Query', function() { }); it('should be converted to a query proto successfully', function(done) { - var q = - ds.createQuery(['Kind']) + var q = ds.createQuery(['Kind']) .select(['name', 'count']) .filter('count >=', datastore.Int(5)) .filter('name =', 'Burcu') diff --git a/test/datastore/transaction.js b/test/datastore/transaction.js new file mode 100644 index 00000000000..563df82d41e --- /dev/null +++ b/test/datastore/transaction.js @@ -0,0 +1,75 @@ +/** + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*global describe, it, beforeEach */ + +'use strict'; + +var assert = require('assert'); +var datastore = require('../../lib').datastore; + +describe('Transaction', function() { + var ds; + var transaction; + + beforeEach(function() { + ds = new datastore.Dataset({ projectId: 'test' }); + transaction = ds.createTransaction(null, 'test'); + }); + + it('should begin', function(done) { + transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'beginTransaction'); + assert.equal(proto, null); + callback(null, 'some-id'); + }; + transaction.begin(done); + }); + + it('should rollback', function(done) { + transaction.id = 'some-id'; + transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'rollback'); + assert.deepEqual(proto, { transaction: 'some-id' }); + callback(); + }; + transaction.rollback(function() { + assert.equal(transaction.isFinalized, true); + done(); + }); + }); + + it('should commit', function(done) { + transaction.id = 'some-id'; + transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'commit'); + assert.deepEqual(proto, { transaction: 'some-id' }); + callback(); + }; + transaction.commit(function() { + assert.equal(transaction.isFinalized, true); + done(); + }); + }); + + it('should be committed if not rolled back', function(done) { + transaction.makeReq = function(method) { + assert.equal(method, 'commit'); + done(); + }; + transaction.finalize(); + }); +}); diff --git a/test/pubsub.js b/test/pubsub/index.js similarity index 98% rename from test/pubsub.js rename to test/pubsub/index.js index 10e90ef1b4c..8b54dd8523c 100644 --- a/test/pubsub.js +++ b/test/pubsub/index.js @@ -19,7 +19,7 @@ 'use strict'; var assert = require('assert'); -var pubsub = require('../lib/pubsub'); +var pubsub = require('../../lib/pubsub'); describe('Subscription', function() { it('should ack messages if autoAck is set', function(done) { diff --git a/test/storage.js b/test/storage/index.js similarity index 98% rename from test/storage.js rename to test/storage/index.js index fa398e9257c..91701470039 100644 --- a/test/storage.js +++ b/test/storage/index.js @@ -19,7 +19,7 @@ 'use strict'; var assert = require('assert'); -var storage = require('../lib').storage; +var storage = require('../../lib').storage; var noop = function() {};