From 0b7132ceb943634f8e8dcffe9687653834888843 Mon Sep 17 00:00:00 2001 From: Ryan Seys Date: Sat, 13 Sep 2014 12:29:22 -0400 Subject: [PATCH] feat(datastore): move Dataset.key() arguments to array Specify parameters in Dataset.key() in an array instead of as a list of arguments. Single parameter can be specified sans in an array. BREAKING CHANGE: Dataset.key() no longer accepts arguments list of parameters and no longer requires null parameters for an unknown identifier when creating incomplete keys. Single kind can still be specified without surrounding in an array. To migrate the code, simply surround arguments in an array and remove the trailing null if you have it. See the example below. Before: var key1 = dataset.key('Company', null); var key2 = dataset.key('kind1', 123, 'kind2', 'id2'); var key3 = dataset.key('kind1', 123, 'kind2', null); After: var key1 = dataset.key('Company'); var key2 = dataset.key(['kind1', 123, 'kind2', 'id2']); var key3 = dataset.key(['kind1', 123, 'kind2']); --- README.md | 4 +- lib/datastore/dataset.js | 62 ++++++++++------ lib/datastore/entity.js | 24 ++++-- lib/datastore/query.js | 4 +- lib/datastore/transaction.js | 23 +++--- regression/datastore.js | 42 +++++------ test/datastore/dataset.js | 74 ++++++++++++------- test/datastore/entity.js | 137 ++++++++++++++++++++--------------- 8 files changed, 218 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 38404a4f50a..fcea8d91d33 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Google Cloud Node.js Client -> Node idiomatic client for Google Cloud services. +> Node.js idiomatic client for Google Cloud services. [![NPM Version](https://img.shields.io/npm/v/gcloud.svg)](https://www.npmjs.org/package/gcloud) [![Travis Build Status](https://travis-ci.org/GoogleCloudPlatform/gcloud-node.svg)](https://travis-ci.org/GoogleCloudPlatform/gcloud-node/) @@ -65,7 +65,7 @@ dataset = new datastore.Dataset({ keyFilename: '/path/to/keyfile.json' }); -dataset.get(dataset.key('Product', 'Computer'), function(err, entity) { +dataset.get(dataset.key(['Product', 'Computer']), function(err, entity) { console.log(err || entity); }); ``` diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js index 5ef2b8609b9..35290fc1367 100644 --- a/lib/datastore/dataset.js +++ b/lib/datastore/dataset.js @@ -111,24 +111,37 @@ function Dataset(options) { * * You may also specify a configuration object to define a namespace and path. * + * @param {...*=} options - Key path. To specify or override a namespace, + * you must use an object here to explicitly state it. + * @param {object=} options - Configuration object. + * @param {...*=} options.path - Key path. + * @param {string=} options.namespace - Optional namespace. + * * @example - * // Create a key from the dataset's namespace. - * var company123 = dataset.key('Company', 123); + * var key; + * + * // Create an incomplete key from the dataset namespace, kind='Company' + * key = dataset.key('Company'); + * + * // A complete key from the dataset namespace, kind='Company', id=123 + * key = dataset.key(['Company', 123]); * - * // Create a key from a provided namespace and path. - * var nsCompany123 = dataset.key({ + * // A complete key from the dataset namespace, kind='Company', name='Google' + * // Note: `id` is used for numeric identifiers and `name` is used otherwise + * key = dataset.key(['Company', 'Google']); + * + * // A complete key from a provided namespace and path. + * key = dataset.key({ * namespace: 'My-NS', * path: ['Company', 123] * }); */ -Dataset.prototype.key = function(keyConfig) { - if (!util.is(keyConfig, 'object')) { - keyConfig = { - namespace: this.namespace, - path: util.toArray(arguments) - }; - } - return new entity.Key(keyConfig); +Dataset.prototype.key = function(options) { + options = util.is(options, 'object') ? options : { + namespace: this.namespace, + path: util.arrayize(options) + }; + return new entity.Key(options); }; @@ -165,8 +178,8 @@ Dataset.prototype.createQuery = function(namespace, kinds) { * * @example * dataset.get([ - * dataset.key('Company', 123), - * dataset.key('Product', 'Computer') + * dataset.key(['Company', 123]), + * dataset.key(['Product', 'Computer']) * ], function(err, entities) {}); */ Dataset.prototype.get = function(key, callback) { @@ -188,7 +201,7 @@ Dataset.prototype.get = function(key, callback) { * @example * // Save a single entity. * dataset.save({ - * key: dataset.key('Company', null), + * key: dataset.key('Company'), * data: { * rating: '10' * } @@ -200,13 +213,13 @@ Dataset.prototype.get = function(key, callback) { * // Save multiple entities at once. * dataset.save([ * { - * key: dataset.key('Company', 123), + * key: dataset.key(['Company', 123]), * data: { * HQ: 'Dallas, TX' * } * }, * { - * key: dataset.key('Product', 'Computer'), + * key: dataset.key(['Product', 'Computer']), * data: { * vendor: 'Dell' * } @@ -228,12 +241,12 @@ Dataset.prototype.save = function(key, obj, callback) { * * @example * // Delete a single entity. - * dataset.delete(dataset.key('Company', 123), function(err) {}); + * dataset.delete(dataset.key(['Company', 123]), function(err) {}); * * // Delete multiple entities at once. * dataset.delete([ - * dataset.key('Company', 123), - * dataset.key('Product', 'Computer') + * dataset.key(['Company', 123]), + * dataset.key(['Product', 'Computer']) * ], function(err) {}); */ Dataset.prototype.delete = function(key, callback) { @@ -277,7 +290,7 @@ Dataset.prototype.runQuery = function(q, callback) { * dataset.runInTransaction(function(transaction, done) { * // From the `transaction` object, execute dataset methods as usual. * // Call `done` when you're ready to commit all of the changes. - * transaction.get(dataset.key('Company', 123), function(err, entity) { + * transaction.get(dataset.key(['Company', 123]), function(err, entity) { * if (err) { * transaction.rollback(done); * return; @@ -308,14 +321,17 @@ Dataset.prototype.runInTransaction = function(fn, callback) { * @example * // The following call will create 100 new IDs from the Company kind, which * // exists under the default namespace. - * var incompleteKey = dataset.key('Company', null); + * var incompleteKey = dataset.key('Company'); * dataset.allocateIds(incompleteKey, 100, function(err, keys) {}); * * // You may prefer to create IDs from a non-default namespace by providing an * // incomplete key with a namespace. Similar to the previous example, the call * // below will create 100 new IDs, but from the Company kind that exists under * // the "ns-test" namespace. - * var incompleteKey = dataset.key('ns-test', 'Company', null); + * var incompleteKey = dataset.key({ + * namespace: 'ns-test', + * path: ['Company'] + * }); * dataset.allocateIds(incompleteKey, 100, function(err, keys) {}); */ Dataset.prototype.allocateIds = function(incompleteKey, n, callback) { diff --git a/lib/datastore/entity.js b/lib/datastore/entity.js index 6c003c90f1d..931c213a40e 100644 --- a/lib/datastore/entity.js +++ b/lib/datastore/entity.js @@ -197,13 +197,21 @@ function keyFromKeyProto(proto) { var keyOptions = { path: [] }; + if (proto.partition_id && proto.partition_id.namespace) { keyOptions.namespace = proto.partition_id.namespace; } - proto.path_element.forEach(function(path) { + + proto.path_element.forEach(function(path, index) { + var id = Number(path.id) || path.name; keyOptions.path.push(path.kind); - keyOptions.path.push(Number(path.id) || path.name || null); + if (id) { + keyOptions.path.push(id); + } else if (index < proto.path_element.length - 1) { + throw new Error('Invalid key. Ancestor keys require an id or name.'); + } }); + return new Key(keyOptions); } @@ -216,7 +224,7 @@ module.exports.keyFromKeyProto = keyFromKeyProto; * @return {object} * * @example - * var keyProto = keyToKeyProto(new Key('Company', 1)); + * var keyProto = keyToKeyProto(new Key(['Company', 1])); * * // keyProto: * // { @@ -230,8 +238,8 @@ module.exports.keyFromKeyProto = keyFromKeyProto; */ function keyToKeyProto(key) { var keyPath = key.path; - if (keyPath.length < 2) { - throw new Error('A key should contain at least a kind and an identifier.'); + if (keyPath.length === 0) { + throw new Error('A key should contain at least a kind.'); } var path = []; for (var i = 0; i < keyPath.length; i += 2) { @@ -244,6 +252,8 @@ function keyToKeyProto(key) { } else { p.id = val; } + } else if (i < keyPath.length - 2) { // i is second last path item + throw new Error('Invalid key. Ancestor keys require an id or name.'); } path.push(p); } @@ -300,8 +310,8 @@ module.exports.formatArray = formatArray; * @return {boolean} * * @example - * isKeyComplete(new Key('Company', 'Google')); // true - * isKeyComplete(new Key('Company', null)); // false + * isKeyComplete(new Key(['Company', 'Google'])); // true + * isKeyComplete(new Key('Company')); // false */ module.exports.isKeyComplete = function(key) { var proto = keyToKeyProto(key); diff --git a/lib/datastore/query.js b/lib/datastore/query.js index 4258c977110..8166791825a 100644 --- a/lib/datastore/query.js +++ b/lib/datastore/query.js @@ -96,7 +96,7 @@ function Query(namespace, kinds) { * * // To filter by key, use `__key__` for the property name. Filter on keys * // stored as properties is not currently supported. - * var keyQuery = query.filter('__key__ =', dataset.key('Company', 'Google')); + * var keyQuery = query.filter('__key__ =', dataset.key(['Company', 'Google'])); */ Query.prototype.filter = function(filter, value) { // TODO: Add filter validation. @@ -122,7 +122,7 @@ Query.prototype.filter = function(filter, value) { * @return {module:datastore/query} * * @example - * var ancestoryQuery = query.hasAncestor(dataset.key('Parent', 123)); + * var ancestoryQuery = query.hasAncestor(dataset.key(['Parent', 123])); */ Query.prototype.hasAncestor = function(key) { var query = extend(new Query(), this); diff --git a/lib/datastore/transaction.js b/lib/datastore/transaction.js index 6c1f512a129..9736d6da669 100644 --- a/lib/datastore/transaction.js +++ b/lib/datastore/transaction.js @@ -80,7 +80,7 @@ function Transaction(conn, datasetId) { * @example * transaction.begin(function(err) { * // Perform Datastore operations as usual. - * transaction.get(dataset.key('Company', 123), function(err, entity) { + * transaction.get(dataset.key(['Company', 123]), function(err, entity) { * // Commit the transaction. * transaction.finalize(function(err) {}); * @@ -189,20 +189,19 @@ Transaction.prototype.finalize = function(callback) { * transaction. Get operations require a valid key to retrieve the * key-identified entity from Datastore. * - * @param {Key|Key[]} key - - * Datastore key object(s). + * @param {Key|Key[]} key - Datastore key object(s). * @param {function} callback - The callback function. * * @example * // These examples work with both a Transaction object and a Dataset object. * * // Get a single entity. - * transaction.get(dataset.key('Company', 123), function(err, entity)); + * transaction.get(dataset.key(['Company', 123]), function(err, entity) {}); * * // Get multiple entities at once. * transaction.get([ - * dataset.key('Company', 123), - * dataset.key('Product', 'Computer') + * dataset.key(['Company', 123]), + * dataset.key(['Product', 'Computer']) * ], function(err, entities) {}); */ Transaction.prototype.get = function(keys, callback) { @@ -256,7 +255,7 @@ Transaction.prototype.get = function(keys, callback) { * * // Save a single entity. * transaction.save({ - * key: dataset.key('Company', null), + * key: dataset.key('Company'), * data: { * rating: '10' * } @@ -268,13 +267,13 @@ Transaction.prototype.get = function(keys, callback) { * // Save multiple entities at once. * transaction.save([ * { - * key: dataset.key('Company', 123), + * key: dataset.key(['Company', 123]), * data: { * HQ: 'Dallas, TX' * } * }, * { - * key: dataset.key('Product', 'Computer'), + * key: dataset.key(['Product', 'Computer']), * data: { * vendor: 'Dell' * } @@ -332,12 +331,12 @@ Transaction.prototype.save = function(entities, callback) { * // These examples work with both a Transaction object and a Dataset object. * * // Delete a single entity. - * transaction.delete(dataset.key('Company', 123), function(err) {}); + * transaction.delete(dataset.key(['Company', 123]), function(err) {}); * * // Delete multiple entities at once. * transaction.delete([ - * dataset.key('Company', 123), - * dataset.key('Product', 'Computer') + * dataset.key(['Company', 123]), + * dataset.key(['Product', 'Computer']) * ], function(err) {}); */ Transaction.prototype.delete = function(keys, callback) { diff --git a/regression/datastore.js b/regression/datastore.js index 0179bdacd71..e81a163b0f3 100644 --- a/regression/datastore.js +++ b/regression/datastore.js @@ -28,7 +28,7 @@ var entity = require('../lib/datastore/entity.js'); describe('datastore', function() { it('should allocate IDs', function(done) { - ds.allocateIds(ds.key('Kind', null), 10, function(err, keys) { + ds.allocateIds(ds.key('Kind'), 10, function(err, keys) { assert.ifError(err); assert.equal(keys.length, 10); assert.equal(entity.isKeyComplete(keys[0]), true); @@ -48,7 +48,7 @@ describe('datastore', function() { }; it('should save/get/delete with a key name', function(done) { - var postKey = ds.key('Post', 'post1'); + var postKey = ds.key(['Post', 'post1']); ds.save({ key: postKey, data: post }, function(err, key) { assert.ifError(err); assert.equal(key.path[1], 'post1'); @@ -64,7 +64,7 @@ describe('datastore', function() { }); it('should save/get/delete with a numeric key id', function(done) { - var postKey = ds.key('Post', 123456789); + var postKey = ds.key(['Post', 123456789]); ds.save({ key: postKey, data: post @@ -84,16 +84,16 @@ describe('datastore', function() { it('should save/get/delete with a generated key id', function(done) { ds.save({ - key: ds.key('Post', null), + key: ds.key('Post'), data: post }, function(err, key) { assert.ifError(err); var assignedId = key.path[1]; assert(assignedId); - ds.get(ds.key('Post', assignedId), function(err, entity) { + ds.get(ds.key(['Post', assignedId]), function(err, entity) { assert.ifError(err); assert.deepEqual(entity.data, post); - ds.delete(ds.key('Post', assignedId), function(err) { + ds.delete(ds.key(['Post', assignedId]), function(err) { assert.ifError(err); done(); }); @@ -111,15 +111,15 @@ describe('datastore', function() { wordCount: 450, rating: 4.5, }; - var key = ds.key('Post', null); + var key = ds.key('Post'); ds.save([ { key: key, data: post }, { key: key, data: post2 } ], function(err, keys) { assert.ifError(err); assert.equal(keys.length,2); - var firstKey = ds.key('Post', keys[0].path[1]); - var secondKey = ds.key('Post', keys[1].path[1]); + var firstKey = ds.key(['Post', keys[0].path[1]]); + var secondKey = ds.key(['Post', keys[1].path[1]]); ds.get([firstKey, secondKey], function(err, entities) { assert.ifError(err); assert.equal(entities.length, 2); @@ -135,7 +135,7 @@ describe('datastore', function() { it('should be able to save keys as a part of entity and query by key', function(done) { - var personKey = ds.key('Person', 'name'); + var personKey = ds.key(['Person', 'name']); ds.save({ key: personKey, data: { @@ -158,14 +158,14 @@ describe('datastore', function() { describe('querying the datastore', function() { var keys = [ - ds.key('Character', 'Rickard'), - ds.key('Character', 'Rickard', 'Character', 'Eddard'), - ds.key('Character', 'Catelyn'), - ds.key('Character', 'Eddard', 'Character', 'Arya'), - ds.key('Character', 'Eddard', 'Character', 'Sansa'), - ds.key('Character', 'Eddard', 'Character', 'Robb'), - ds.key('Character', 'Eddard', 'Character', 'Bran'), - ds.key('Character', 'Eddard', 'Character', 'Jon Snow') + ds.key(['Character', 'Rickard']), + ds.key(['Character', 'Rickard', 'Character', 'Eddard']), + ds.key(['Character', 'Catelyn']), + ds.key(['Character', 'Eddard', 'Character', 'Arya']), + ds.key(['Character', 'Eddard', 'Character', 'Sansa']), + ds.key(['Character', 'Eddard', 'Character', 'Robb']), + ds.key(['Character', 'Eddard', 'Character', 'Bran']), + ds.key(['Character', 'Eddard', 'Character', 'Jon Snow']) ]; var characters = [{ @@ -265,7 +265,7 @@ describe('datastore', function() { it('should filter by ancestor', function(done) { var q = ds.createQuery('Character') - .hasAncestor(ds.key('Character', 'Eddard')); + .hasAncestor(ds.key(['Character', 'Eddard'])); ds.runQuery(q, function(err, entities) { assert.ifError(err); assert.equal(entities.length, 5); @@ -275,7 +275,7 @@ describe('datastore', function() { it('should filter by key', function(done) { var q = ds.createQuery('Character') - .filter('__key__ =', ds.key('Character', 'Rickard')); + .filter('__key__ =', ds.key(['Character', 'Rickard'])); ds.runQuery(q, function(err, entities) { assert.ifError(err); assert.equal(entities.length, 1); @@ -370,7 +370,7 @@ describe('datastore', function() { describe('transactions', function() { it('should run in a transaction', function(done) { - var key = ds.key('Company', 'Google'); + var key = ds.key(['Company', 'Google']); var obj = { url: 'www.google.com' }; diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index ae2325e9bb5..7b8695f45af 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -53,21 +53,43 @@ describe('Dataset', function() { assert.deepEqual(ds.connection.credentials, uniqueCredentials); }); - it('should return a key scoped by namespace', function() { - var ds = datastore.dataset({ projectId: 'test', namespace: 'my-ns' }); - var key = ds.key('Company', 1); - assert.equal(key.namespace, 'my-ns'); - assert.deepEqual(key.path, ['Company', 1]); - }); + describe('key', function() { + it('should return key scoped by default namespace', function() { + var ds = datastore.dataset({ projectId: 'test', namespace: 'my-ns' }); + var key = ds.key(['Company', 1]); + assert.equal(key.namespace, 'my-ns'); + assert.deepEqual(key.path, ['Company', 1]); + }); - it('should allow namespace specification when creating a key', function() { - var ds = datastore.dataset({ projectId: 'test' }); - var key = ds.key({ - namespace: 'custom-ns', - path: ['Company', 1] + it('should allow namespace specification', function() { + var ds = datastore.dataset({ projectId: 'test', namespace: 'my-ns' }); + var key = ds.key({ + namespace: 'custom-ns', + path: ['Company', 1] + }); + assert.equal(key.namespace, 'custom-ns'); + assert.deepEqual(key.path, ['Company', 1]); + }); + + it('should create incomplete key from string', function() { + var ds = datastore.dataset({ projectId: 'test' }); + var key = ds.key('hello'); + assert.deepEqual(key.path, ['hello']); + }); + + it('should create incomplete key from array in obj', function() { + var ds = datastore.dataset({ projectId: 'test' }); + var key = ds.key({ + path: ['world'] + }); + assert.deepEqual(key.path, ['world']); + }); + + it('should create incomplete key from array', function() { + var ds = datastore.dataset({ projectId: 'test' }); + var key = ds.key(['Company']); + assert.deepEqual(key.path, ['Company']); }); - assert.equal(key.namespace, 'custom-ns'); - assert.deepEqual(key.path, ['Company', 1]); }); it('should get by key', function(done) { @@ -77,7 +99,7 @@ describe('Dataset', function() { assert.equal(proto.key.length, 1); callback(null, mockRespGet); }; - ds.get(ds.key('Kind', 123), function(err, entity) { + ds.get(ds.key(['Kind', 123]), function(err, entity) { var data = entity.data; assert.deepEqual(entity.key.path, ['Kind', 5732568548769792]); assert.strictEqual(data.author, 'Silvano'); @@ -94,7 +116,7 @@ describe('Dataset', function() { assert.equal(proto.key.length, 1); callback(null, mockRespGet); }; - var key = ds.key('Kind', 5732568548769792); + var key = ds.key(['Kind', 5732568548769792]); ds.get([key], function(err, entities) { var entity = entities[0]; var data = entity.data; @@ -108,8 +130,8 @@ describe('Dataset', function() { it('should continue looking for deferred results', function(done) { var ds = datastore.dataset({ projectId: 'test' }); - var key = ds.key('Kind', 5732568548769792); - var key2 = ds.key('Kind', 5732568548769792); + var key = ds.key(['Kind', 5732568548769792]); + var key2 = ds.key(['Kind', 5732568548769792]); var lookupCount = 0; ds.transaction.makeReq = function(method, proto, typ, callback) { lookupCount++; @@ -136,7 +158,7 @@ describe('Dataset', function() { assert.equal(!!proto.mutation.delete, true); callback(); }; - ds.delete(ds.key('Kind', 123), done); + ds.delete(ds.key(['Kind', 123]), done); }); it('should multi delete by keys', function(done) { @@ -147,8 +169,8 @@ describe('Dataset', function() { callback(); }; ds.delete([ - ds.key('Kind', 123), - ds.key('Kind', 345) + ds.key(['Kind', 123]), + ds.key(['Kind', 345]) ], done); }); @@ -159,7 +181,7 @@ describe('Dataset', function() { assert.equal(proto.mutation.insert_auto_id.length, 1); callback(); }; - var key = ds.key('Kind', null); + var key = ds.key('Kind'); ds.save({ key: key, data: {} }, done); }); @@ -174,8 +196,8 @@ describe('Dataset', function() { callback(); }; ds.save([ - { key: ds.key('Kind', 123), data: { k: 'v' } }, - { key: ds.key('Kind', 456), data: { k: 'v' } } + { key: ds.key(['Kind', 123]), data: { k: 'v' } }, + { key: ds.key(['Kind', 456]), data: { k: 'v' } } ], done); }); @@ -194,8 +216,8 @@ describe('Dataset', function() { ] }); }; - ds.allocateIds(ds.key('Kind', null), 1, function(err, ids) { - assert.deepEqual(ids[0], ds.key('Kind', 123)); + ds.allocateIds(ds.key('Kind'), 1, function(err, ids) { + assert.deepEqual(ids[0], ds.key(['Kind', 123])); done(); }); }); @@ -203,7 +225,7 @@ describe('Dataset', function() { it('should throw if trying to allocate IDs with complete keys', function() { var ds = datastore.dataset({ projectId: 'test' }); assert.throws(function() { - ds.allocateIds(ds.key('Kind', 123)); + ds.allocateIds(ds.key(['Kind', 123])); }); }); diff --git a/test/datastore/entity.js b/test/datastore/entity.js index 2ac8fa9ea30..da2e3798a29 100644 --- a/test/datastore/entity.js +++ b/test/datastore/entity.js @@ -37,7 +37,7 @@ var entityProto = { 'key_value': { 'path_element': [{ 'kind': 'Kind', - 'name': 'another' + 'name': '123' }] } } @@ -121,28 +121,25 @@ var queryFilterProto = { }; describe('registerKind', function() { - it('should be able to register valid field metadata', function(done) { + it('should be able to register valid field metadata', function() { entity.registerKind('namespace', 'kind', blogPostMetadata); - done(); }); - it('should set the namespace to "" if zero value or null', function(done) { + it('should set the namespace to "" if zero value or null', function() { entity.registerKind(null, 'kind', blogPostMetadata); var meta = entity.getKind('', 'kind'); assert.strictEqual(meta, blogPostMetadata); - done(); }); - it('should throw an exception if an invalid kind', function(done) { + it('should throw an exception if an invalid kind', function() { assert.throws(function() { entity.registerKind(null, '000', blogPostMetadata); }, /Kinds should match/); - done(); }); }); describe('keyFromKeyProto', function() { - var proto = { + var proto = { partition_id: { namespace: '', dataset_id: 'datasetId' }, path_element: [{ kind: 'Kind', name: 'Name' }] }; @@ -152,38 +149,46 @@ describe('keyFromKeyProto', function() { path_element: [{ kind: 'Kind', id: '111' }, { kind: 'Kind2', name: 'name' }] }; - var protoHIncomplete = { + var protoIncomplete = { + partition_id: { namespace: 'Test', dataset_id: 'datasetId' }, + path_element: [{ kind: 'Kind', id: '111' }, { kind: 'Kind2' }] + }; + + var protoInvalid = { partition_id: { namespace: 'Test', dataset_id: 'datasetId' }, path_element: [{ kind: 'Kind' }, { kind: 'Kind2' }] }; - it('should handle keys hierarchically', function(done) { + it('should handle keys hierarchically', function() { var key = entity.keyFromKeyProto(protoH); assert.deepEqual(key, new entity.Key({ - namespace: 'Test', - path: [ 'Kind', 111, 'Kind2', 'name' ] - })); - done(); + namespace: 'Test', + path: [ 'Kind', 111, 'Kind2', 'name' ] + })); }); - it('should handle incomplete keys hierarchically', function(done) { - var key = entity.keyFromKeyProto(protoHIncomplete); + it('should not set namespace if default', function() { + var key = entity.keyFromKeyProto(proto); + assert.deepEqual(key, new entity.Key({ path: [ 'Kind', 'Name' ] })); + }); + + it('should not inject null into path if no id set', function(){ + var key = entity.keyFromKeyProto(protoIncomplete); assert.deepEqual(key, new entity.Key({ - namespace: 'Test', - path: [ 'Kind', null, 'Kind2', null ] - })); - done(); + namespace: 'Test', + path: [ 'Kind', 111, 'Kind2' ] + })); }); - it('should not set namespace if default', function(done) { - var key = entity.keyFromKeyProto(proto); - assert.deepEqual(key, new entity.Key({ path: [ 'Kind', 'Name' ] })); - done(); + it('should throw if path is invalid', function() { + assert.throws(function() { + entity.keyFromKeyProto(protoInvalid); + }, /Invalid key. Ancestor keys require an id or name./); }); }); describe('keyToKeyProto', function() { - it('should handle hierarchical key definitions', function(done) { + it('should handle hierarchical key definitions', function() { var key = new entity.Key({ path: [ 'Kind1', 1, 'Kind2', 'name' ] }); var proto = entity.keyToKeyProto(key); assert.strictEqual(proto.partition_id, undefined); @@ -193,10 +198,9 @@ describe('keyToKeyProto', function() { assert.strictEqual(proto.path_element[1].kind, 'Kind2'); assert.strictEqual(proto.path_element[1].id, undefined); assert.strictEqual(proto.path_element[1].name, 'name'); - done(); }); - it('should detect the namespace of the hierarchical keys', function(done) { + it('should detect the namespace of the hierarchical keys', function() { var key = new entity.Key({ namespace: 'Namespace', path: [ 'Kind1', 1, 'Kind2', 'name' ] @@ -209,14 +213,13 @@ describe('keyToKeyProto', function() { assert.strictEqual(proto.path_element[1].kind, 'Kind2'); assert.strictEqual(proto.path_element[1].id, undefined); assert.strictEqual(proto.path_element[1].name, 'name'); - done(); }); - it('should handle incomplete keys with & without namespaces', function(done) { - var key = new entity.Key({ path: [ 'Kind1', null ] }); + it('should handle incomplete keys with & without namespaces', function() { + var key = new entity.Key({ path: [ 'Kind1' ] }); var keyWithNS = new entity.Key({ namespace: 'Namespace', - path: [ 'Kind1', null ] + path: [ 'Kind1' ] }); var proto = entity.keyToKeyProto(key); @@ -231,43 +234,65 @@ describe('keyToKeyProto', function() { assert.strictEqual(protoWithNS.path_element[0].kind, 'Kind1'); assert.strictEqual(protoWithNS.path_element[0].id, undefined); assert.strictEqual(protoWithNS.path_element[0].name, undefined); - done(); }); - it('should throw if key contains less than 2 items', function() { + it('should throw if key contains 0 items', function() { assert.throws(function() { - entity.keyToKeyProto(['Kind']); + var key = new entity.Key({ path: [] }); + entity.keyToKeyProto(key); + }, /A key should contain at least a kind/); + }); + + it('should throw if key path contains null ids', function() { + assert.throws(function() { + var key = new entity.Key({ + namespace: 'Namespace', + path: [ 'Kind1', null, 'Company' ] + }); + entity.keyToKeyProto(key); + }, /Invalid key. Ancestor keys require an id or name./); + }); + + it('should not throw if last key path item is null', function() { + assert.doesNotThrow(function() { + var key = new entity.Key({ + namespace: 'Namespace', + path: [ 'Kind1', 123, 'Company', null ] + }); + entity.keyToKeyProto(key); }); }); }); describe('isKeyComplete', function() { - it('should ret true if kind and an identifier have !0 vals', function(done) { + it('should ret true if kind and an identifier have !0 vals', function() { [ - { key: new entity.Key({ path: [ 'Kind1', null ] }), expected: false }, - { key: new entity.Key({ path: [ 'Kind1', 3 ] }), expected: true }, - { key: new entity.Key({ - namespace: 'Namespace', - path: [ 'Kind1', null ] - }), expected: false }, - { key: new entity.Key({ - namespace: 'Namespace', - path: [ 'Kind1', 'name' ] - }), expected: true } + { + key: new entity.Key({ path: [ 'Kind1' ] }), + expected: false + }, + { + key: new entity.Key({ path: [ 'Kind1', 3 ] }), + expected: true + }, + { + key: new entity.Key({ namespace: 'NS', path: [ 'Kind1' ] }), + expected: false + }, + { + key: new entity.Key({ namespace: 'NS', path: [ 'Kind1', 'name' ] }), + expected: true + } ].forEach(function(test) { assert.strictEqual(entity.isKeyComplete(test.key), test.expected); }); - done(); }); }); describe('entityFromEntityProto', function() { - it('should support boolean, integer, double, string, entity and list values', - function(done) { + it('should support bool, int, double, str, entity & list values', function() { var obj = entity.entityFromEntityProto(entityProto); - assert.deepEqual(obj.linkedTo, new entity.Key({ - path: [ 'Kind', 'another' ] - })); + assert.deepEqual(obj.linkedTo, new entity.Key({ path: [ 'Kind', '123' ]})); assert.strictEqual(obj.name, 'Some name'); assert.strictEqual(obj.flagged, false); assert.strictEqual(obj.count, 5); @@ -275,14 +300,11 @@ describe('entityFromEntityProto', function() { assert.strictEqual(obj.author.name, 'Burcu Dogan'); assert.strictEqual(obj.list[0], 6); assert.strictEqual(obj.list[1], false); - done(); }); }); describe('entityToEntityProto', function() { - it( - 'should support boolean, integer, double, string, entity and list values', - function(done) { + it('should support bool, int, double, str, entity & list values', function() { var now = new Date(); var proto = entity.entityToEntityProto({ name: 'Burcu', @@ -315,19 +337,16 @@ describe('entityToEntityProto', function() { var entityValue = properties[8].value.entity_value; assert.equal(entityValue.property[0].value.string_value, 'value1'); assert.equal(entityValue.property[1].value.string_value, 'value2'); - done(); }); - }); describe('queryToQueryProto', function() { - it('should support filters and ancestory filtering', function(done) { + it('should support filters and ancestory filtering', function() { var ds = datastore.dataset({ projectId: 'project-id' }); var q = ds.createQuery('Kind1') .filter('name =', 'John') .hasAncestor(new entity.Key({ path: [ 'Kind2', 'somename' ] })); var proto = entity.queryToQueryProto(q); assert.deepEqual(proto, queryFilterProto); - done(); }); });