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(); }); });