From 2683abdf01abdf2b1651776fdd84aab3eaf6f0b5 Mon Sep 17 00:00:00 2001 From: Silvano Luciani Date: Fri, 1 Aug 2014 12:34:26 -0700 Subject: [PATCH] test(storage): add regression tests. --- CONTRIBUTING.md | 1 + README.md | 43 +++-- lib/common/util.js | 32 +++- lib/datastore/entity.js | 9 + lib/datastore/index.js | 34 ++-- lib/pubsub/index.js | 9 +- lib/storage/index.js | 63 ++----- package.json | 2 +- .../data/CloudPlatform_128px_Retina.png | Bin 0 -> 9587 bytes regression/datastore.js | 100 +++++----- regression/env.js | 4 +- regression/storage.js | 173 ++++++++++++++++++ test/common.util.js | 43 ++++- test/datastore.js | 104 +++++++++-- 14 files changed, 459 insertions(+), 158 deletions(-) create mode 100644 regression/data/CloudPlatform_128px_Retina.png create mode 100644 regression/storage.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e5835a4f6e..1a9d2a0d95c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ To run the regression tests, first create and configure a project following the After that, set the following environment variables: - **GCLOUD_TESTS_PROJECT_ID**: Developers Console project's ID (e.g. bamboo-shift-455) +- **GCLOUD_TESTS_BUCKET_NAME**: The name of the bucket to use for the Cloud Storage API tests - **GCLOUD_TESTS_KEY**: The path to the JSON key file. Lastly, create the indexes used in the datastore regression tests using the [gcloud command-line tool](https://developers.google.com/cloud/sdk/gcloud/) and the indexes that you can find in `regression/data/index/yaml`: diff --git a/README.md b/README.md index a331b97956c..3dc41ad9836 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Node idiomatic client for Google Cloud services. Work in progress... Watch the r This client supports the following Google Cloud services: -* [Google Cloud Datastore](https://developers.google.com/datastore/) (also allows access to the collections of your existing AppEngine apps) +* [Google Cloud Datastore](https://developers.google.com/datastore/) * [Google Cloud Storage](https://cloud.google.com/products/cloud-storage/) * [Google Cloud Pub/Sub](https://developers.google.com/pubsub/) * Planned but not yet started: [Google Compute Engine](https://developers.google.com/compute), and [Google BigQuery](https://developers.google.com/bigquery/) @@ -67,11 +67,7 @@ The downloaded file contains credentials you'll need for authorization. ### Google Cloud Datastore -Google Cloud Datastore is a NoSQL database with the -convenience of a traditional RDBMS in terms of strong -consistency guarantees and high availability. It's also known -as Megastore. Its performance characteristics are explained -in detail on [Megastore: Providing Scalable, Highly Available Storage for Interactive Services](http://www.cidrdb.org/cidr2011/Papers/CIDR11_Paper32.pdf). +[Google Cloud Datastore](https://developers.google.com/datastore/) is a fully managed, schemaless database for storing non-relational data. Cloud Datastore automatically scales with your users and supports ACID transactions, high availability of reads and writes, strong consistency for reads and ancestor queries, and eventual consistency for all other queries. #### Configuration @@ -101,11 +97,11 @@ TODO Get operations require a valid key to retrieve the key identified entity from Datastore. Skip to the "Querying" section if you'd like to learn more about querying against Datastore. ~~~~ js -ds.get(['Company', 123], function(err, key, obj) { +ds.get(['Company', 123], function(err, entities) { }); // alternatively, you can retrieve multiple entities at once. -ds.getAll([key1, key2, ...], function(err, keys, objs) { +ds.getAll([key1, key2, ...], function(err, entities) { }); ~~~~ @@ -150,7 +146,7 @@ also supported. ~~~~ js // retrieves 5 companies var q = ds.createQuery('Company').limit(5); -ds.runQuery(q, function(err, keys, objs, nextQuery) { +ds.runQuery(q, function(err, entities, nextQuery) { // nextQuery is not null if there are more results. if (nextQuery) { ds.runQuery(nextQuery, callback); @@ -172,6 +168,14 @@ var q = ds.createQuery('Company') .filter('size <', 400); ~~~~ +To filter by key, use `__key__` for the property name. Filtering on keys +stored as properties is not currently supported. + +~~~~ js +var q = ds.createQuery('Company') + .filter('__key__ =', ['Company', 'Google']) +~~~~ + In order to filter by ancestors, use `hasAncestor` helper. ~~~ js @@ -341,16 +345,21 @@ A bucket object allows you to write a readable stream, a file and a buffer as file contents. ~~~~ js -// Uploads file.pdf -bucket.writeFile( - filename, '/path/to/file.pdf', { contentType: 'application/pdf' }, callback); +// Uploads file.pdf. +bucket.write(name, { + filename: '/path/to/file.pdf', + metadata: { /* metadata properties */ } +}, callback); -// Reads the stream and uploads it as file contents -bucket.writeStream( - filename, fs.createReadStream('/path/to/file.pdf'), metadata, callback); +// Uploads the readable stream. +bucket.write(name, { + data: anyReadableStream, + metadata: { /* metadata properties */ } +}, callback); -// Uploads 'Hello World' as file contents -bucket.writeBuffer(filename, 'Hello World', callback); +// Uploads 'Hello World' as file contents. +// data could be any string or buffer. +bucket.write(name, { data: 'Hello World' }, callback); ~~~~ #### Copy files diff --git a/lib/common/util.js b/lib/common/util.js index 438ebfbdfb5..6cb1d46abb3 100644 --- a/lib/common/util.js +++ b/lib/common/util.js @@ -49,14 +49,38 @@ module.exports.format = function(templ, args) { return templ; }; -module.exports.noop = function(){}; +var noop = function() {}; -module.exports.ApiError = function (errorBody) { +module.exports.noop = noop; + +function ApiError (errorBody) { Error.call(this); Error.captureStackTrace(this, arguments.callee); this.errors = errorBody.errors; this.code = errorBody.code; this.message = errorBody.message; -} +}; -util.inherits(module.exports.ApiError, Error); +util.inherits(ApiError, Error); + +module.exports.handleResp = function(err, resp, body, opt_callback) { + var callback = opt_callback || noop; + if (err) { + callback(err); + return; + } + if (typeof body === 'string') { + try { + body = JSON.parse(body); + } catch(err) {} + } + if (body && body.error) { + callback(new ApiError(body.error)); + return; + } + if (resp && (resp.statusCode < 200 || resp.statusCode > 299)) { + callback(new Error('error during request, statusCode: ' + resp.statusCode)); + return; + } + callback(null, body, resp); +}; diff --git a/lib/datastore/entity.js b/lib/datastore/entity.js index f1c2e7e06d9..350df990a92 100644 --- a/lib/datastore/entity.js +++ b/lib/datastore/entity.js @@ -110,6 +110,15 @@ var keyToKeyProto = function(datasetId, key) { module.exports.keyToKeyProto = keyToKeyProto; +module.exports.formatArray = function(results) { + return results.map(function (result) { + return { + key: keyFromKeyProto(result.entity.key), + data: entityFromEntityProto(result.entity) + }; + }); +}; + module.exports.isKeyComplete = function(key) { var proto = keyToKeyProto(null, key); for (var i = 0; i < proto.path.length; i++) { diff --git a/lib/datastore/index.js b/lib/datastore/index.js index 7d54d38ea1a..de7d4889a04 100644 --- a/lib/datastore/index.js +++ b/lib/datastore/index.js @@ -99,11 +99,11 @@ Transaction.prototype.finalize = function(callback) { * @param {Function} callback */ Transaction.prototype.get = function(key, callback) { - this.getAll([key], function(err, keys, objs) { - if (err || objs.length < 0) { + this.getAll([key], function(err, results) { + if (err) { return callback(err); } - return callback(null, keys[0], objs[0]); + return callback(null, results[0]); }); }; @@ -114,6 +114,7 @@ Transaction.prototype.get = function(key, callback) { * @param {Function} callback */ Transaction.prototype.getAll = function(keys, callback) { + callback = callback || util.noop; keysProto = []; keys.forEach(function(k) { keysProto.push(entity.keyToKeyProto(this.id, k)); @@ -127,12 +128,8 @@ Transaction.prototype.getAll = function(keys, callback) { if (err) { return callback(err); } - var results = [], keys = []; - resp.found.forEach(function(f) { - keys.push(entity.keyFromKeyProto(f.entity.key)); - results.push(entity.entityFromEntityProto(f.entity)); - }); - callback && callback(null, keys, results); + + callback(null, entity.formatArray(resp.found)); }); }; @@ -245,7 +242,7 @@ Transaction.prototype.delAll = function(keys, callback) { * available, a query to retrieve the next page is provided to * the callback function. Example: * - * t.runQuery(q, function(err, keys, objs, nextQuery) { + * t.runQuery(q, function(err, entities, nextQuery) { * if (err) return; * // if next page is available, retrieve more results * if (nextQuery) { @@ -256,6 +253,7 @@ Transaction.prototype.delAll = function(keys, callback) { * @param {Function} opt_callback */ Transaction.prototype.runQuery = function(q, opt_callback) { + opt_callback = opt_callback || util.noop; var req = { readOptions: { transaction: this.id @@ -269,19 +267,13 @@ Transaction.prototype.runQuery = function(q, opt_callback) { } this.makeReq('runQuery', req, function(err, resp) { if (err || !resp.batch || !resp.batch.entityResults) { - return opt_callback && opt_callback(err); + return opt_callback(err); } - var results = resp.batch.entityResults, - keys = [], objs = []; - results.forEach(function(r) { - keys.push(entity.keyFromKeyProto(r.entity.key)); - objs.push(entity.entityFromEntityProto(r.entity)); - }); var nextQuery = null; if (resp.batch.endCursor && resp.batch.endCursor != q.startVal) { nextQuery = q.start(resp.batch.endCursor); } - opt_callback && opt_callback(err, keys, objs, nextQuery); + opt_callback(null, entity.formatArray(resp.batch.entityResults), nextQuery); }); }; @@ -301,11 +293,7 @@ Transaction.prototype.makeReq = function(method, req, callback) { uri: DATASTORE_BASE_URL + '/' + this.datasetId + '/' + method, json: req }, function(err, res, body) { - if (body && body.error) { - var error = new util.ApiError(body.error); - return callback(error, null); - } - callback(err, body); + util.handleResp(err, res, body, callback); }); }; diff --git a/lib/pubsub/index.js b/lib/pubsub/index.js index ebaeb838fdc..504876d9486 100644 --- a/lib/pubsub/index.js +++ b/lib/pubsub/index.js @@ -398,7 +398,6 @@ Connection.prototype.fullProjectName_ = function() { }); }; -// TOOD(jbd): Don't duplicate, unify this with bucket.makeReq. Connection.prototype.makeReq = function(method, path, q, body, callback) { var reqOpts = { method: method, @@ -412,13 +411,7 @@ Connection.prototype.makeReq = function(method, path, q, body, callback) { reqOpts.json = body; } this.conn.req(reqOpts, function(err, res, body) { - if (body && body.error) { - callback(new util.ApiError(body.error)); return; - } - if (res && (res.statusCode < 200 || res.statusCode > 299)) { - callback(new Error('error during request, statusCode: ' + res.statusCode)); return; - } - callback(null, body); + util.handleResp(err, res, body, callback); }); }; diff --git a/lib/storage/index.js b/lib/storage/index.js index 819fa24c5b6..856e2f33f74 100644 --- a/lib/storage/index.js +++ b/lib/storage/index.js @@ -33,16 +33,12 @@ var STORAGE_BASE_URL = 'https://www.googleapis.com/storage/v1/b'; STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b'; var reqStreamToCallback = function(st, callback) { + st.callback = util.noop; st.on('error', function(err) { callback(err); }); st.on('complete', function(resp) { - // TODO(jbd): Buffer the response to pass the resp body - // to the callback. - if (resp.statusCode < 200 || resp.statusCode > 299) { - return callback(new Error('error during request, statusCode: ' + resp.statusCode), resp); - } - callback(null, resp); + util.handleResp(null, resp, resp.body, callback); }); }; @@ -254,17 +250,25 @@ Bucket.prototype.createReadStream = function(name) { /** * Writes the provided stream to the destination * with optional metadata. - * @param {String} name Name of the remote file. - * @param {Stream} stream A readable stream. - * @param {Object?} metadata Optional metadata. + * @param {String} name Name of the remote file. + * @param {Object=} opts.data A string, buffer or readable stream. + * @param {string=} opts.filename Path of the source file. + * @param {Object=} opts.metadata Optional metadata. * @param {Function} callback Callback function. */ -Bucket.prototype.writeStream = function(name, stream, metadata, callback) { - if (!callback) { - callback = metadata, metadata = {}; +Bucket.prototype.write = function(name, opts, callback) { + // TODO(jbd): Support metadata only requests. + var that = this; + + var metadata = opts.metadata || {}; + var stream = opts.data; + + if (opts.filename) { + stream = fs.createReadStream(opts.filename); + } else if (opts.data && (typeof opts.data === 'string' || opts.data instanceof Buffer)) { + stream = new BufferStream(opts.data); } - var that = this; var boundary = uuid.v4(); metadata.contentType = metadata.contentType || 'text/plain' this.conn.createAuthorizedReq({ @@ -298,34 +302,11 @@ Bucket.prototype.writeStream = function(name, stream, metadata, callback) { remoteStream.write('Content-Type: ' + metadata.contentType + '\n\n'); stream.pipe(remoteStream); // TODO(jbd): High potential of multiple callback invokes. - reqStreamToCallback(stream, callback); + stream.on('error', callback); reqStreamToCallback(remoteStream, callback); }); }; -/** - * Writes the source file to the destination with - * optional metadata. - * @param {String} name Name of the remote file. - * @param {String} filename Path to the source file. - * @param {object?} metadata Optional metadata. - * @param {Function} callback Callback function. - */ -Bucket.prototype.writeFile = function(name, filename, metadata, callback) { - this.writeStream(name, fs.createReadStream(filename), metadata, callback); -}; - -/** - * Writes the provided buffer to the destination file. - * @param {String} name Name of the remote file resource. - * @param {Buffer} buffer Buffer contents to be written. - * @param {Object?} metadata Optional metadata. - * @param {Function} callback Callback function. - */ -Bucket.prototype.writeBuffer = function(name, buffer, metadata, callback) { - this.writeStream(name, new BufferStream(buffer), metadata, callback); -}; - /** * Makes a new request object from the provided * arguments, and wraps the callback to intercept @@ -345,13 +326,7 @@ Bucket.prototype.makeReq = function(method, path, q, body, callback) { reqOpts.json = body; } this.conn.req(reqOpts, function(err, res, body) { - if (body && body.error) { - return callback(body.error); - } - if (res && (res.statusCode < 200 || res.statusCode > 299)) { - return callback(new Error('error during request, statusCode: ' + res.statusCode)); - } - callback(err, body); + util.handleResp(err, res, body, callback); }); }; diff --git a/package.json b/package.json index 3afa13ea940..1881fe9167c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gcloud", - "version": "0.0.6", + "version": "0.1.0", "author": "Google Inc.", "description": "Google Cloud APIs Client Library for Node.js", "contributors": [ diff --git a/regression/data/CloudPlatform_128px_Retina.png b/regression/data/CloudPlatform_128px_Retina.png new file mode 100644 index 0000000000000000000000000000000000000000..86c04e4b44f4ca4dea8214b9bb8ecc2108e211b2 GIT binary patch literal 9587 zcma)iRZtwj(k{W>-JL*i2@WAZa0u?cKyYVqO>lP!9^4`5;sh3l2(qw1&<$kKAPb9J z&VQ@^hx>BwRLyihb&q`0T|M(9yw_GCz@^1SK|vuIPGfmzMn*2zK7!4?qYJ>?*Sg2F+erlepHxCRfy2{l+6cshkT zT*F_nN1?xE#!l30XkvcS=;@zUhvYfef-p#4C`>W-qY1dA?g|050u&%Do+(iGn z=;dfUK5TTQHwvgxZS2lTGURQBCy9n7;=4HGTE09 z9d+B9DS1+xnHYkDj+#;Y=D#Vd($BWr$*|G?KNO{a=ZOHgh_U~R@*nr{aCFqD|EnmH zUCjjqV_8^8?7ZqSV^%|x^qzf@E8BHNva?;GJ<(x3HkWd%%)cpkD5qN@*Ryld1x> ztdiEt`(2)0*y*prm5PG~b@+KCtd&Ucl7(Jnd2yQS)I*4i3EmFVdYR#A$iAvd`3CUx z0l#nW{$-eV@wtyZo&2lc4=es+y4$kqZbx@srC}qRl*rD*-6cso{7n`OiSZJ4f{}!Y zf_c2%%lHwrh%Gm{pQ|2t6%nEn6fvZwxWuo^5KQZ{P9c8O<5j5gQep!{P$xvn3`x0wg8cYjQJtxCP0DcGrm2^+LHZ zMe*yyoQW_?TEKXw*MOr{e8w>8KLXCbezMoJ&hhbNnG&pWB7HCuO$m4(7%!)l^c+gN z8wj2DZr%r|ju@d%V1(d4N!URAOq1 zaJO~1=x*S=Dvd{w@Qx@?(2hx=j%{mT%K3nZit2(9xoQe#opi>7!DwX<-OF82xyIux z&u-GG_$myGEE}SPPn0!X#ik^mOtXZ0e_AB5>eO7-13dHA% z9w})YQ}Xn^NhlrSqyOhd`XU71Y6}qV5ZrmO*R%a2xEtrG1v>TyphJ?v0NtO8v4w_# zqqISyrga2d&u(AKJm{c#^e|KI1t?3%T*sb#yGYo%SFl$V_LW0^6Q)u3vD+d;J{gC8 zh+Y`WjFr$$3i;;B)bHK}6K474@~mS$Wh>M;&hG~D_MmfNk-sC4}%h{YZt z?Emfai(v@sGT*FSt3wL`NU8s?eq1C=f(BXnI|a^|j(6(B38;-5&QT8I(5oyK-jb){ znE_we=LX&rZ`}{q*iw2sSELt6Kb$DR z`%h{#a9c0TU$G%9H?I1Z+>4nsyNVB{2+6IzwlNdg10Fr)cv8PF@B~2_Y5!g9TlT;U zzK7htm}^rOrWEH=F%zG=gMX9*_RxnbosdEL?~)nC*|K4)IXl{7A6n;%1@DnbomZA5 zR$tPoAVb9Q(zu?XfAvl;g~r_+>Qi3BvVRgVJLp7KkQ=E<5{Qm=F`6?L{|7M`Kg)Pt zGXd21eQJmfcr}w61e!!Bd3)x?e>OxrKmd74*P_f6D;UNt{9a~w96%Cnl z6wc`g`yG7~6RGqK1D*3Za-#KB`p?&Qac7>Tj80E1IeHehoW18t*XSRP=&4go8h=fM zcaH`R3y$mt2!NbLCNB%_U128&ENM8)&c=w<6XFam#{KJ5GfXRBz&5cr2X!Ko-0Wxc z83@6R7QM((&UeUSwfqtV~#dS2>2#C^WJS{n{H6FLd9cObuOfzNmdNHH?`YvXEkns5 z`w)?mrJQ=P*d5Mf+R>e346<}ltVo+pJoD6naBJpujF5MW3^83=KZ$o64~{bvd(C8| zHN6sKwYmG($HVlv4@OnSSa%zEdJJ8z^$~W_yGDGV9@Z9H{rC7bbW7fVckSa>O4`3YS(KJX#*M7Jt#qz*UK8xHC2w zE>|$XB{wVL=0M=}8MJJMPIc#~CDLUKULPY^Tgj(|c3T0lX)+xzKSV(@l&wn&ml>gX zxl`je>zj%&-jPfes=ETF$^WJ?Z9~=6hXrg62%N6E2p=hyHHnANv2`OuxrO8QnO3R# z&!cX8{Tho>u{nlX`*YGJQw5XPvG_i^@;NvN?O#YTPv)op4xEbaS1^sHH;l4K!=*`t z_rT%#u+QmKXSZ*VUNg500piKq|48UXH|G_ag{|XjWuAQ&ZVo+GbG=2T z{4f|gI+37#9b+*xb(-I&SPL5G4M}&(DPF5Yz0+ab3!UMd2vf;b4FSfc7X1G5J) zRqhKiR~t~eA-|%Hjg6W-63A4F&ZzLi9+ihWq?e_p+HrqCcLvL`2d9UF(dh)==~s3c zkHGB0D_46GU+fK55>SUXnaPJE{1VTibPfyuQx>X?la@@dh*1PeU+&OdXGx(LSsSj6 zN+s6-_H$U-rd!9tAmcxpf4U2BQaS&ic0b?rT5xo(4BbaTFO-{GCe8$OyKhgFXp$S_ zNZ3|Q{1XNFqv6GCca%-C0CI?|bLMdBCHk}F>o*h1c~+l3VinQC)F4>uufrmCUh(iZ zkim1s>&vzmws;Bszfo3WD}z54lX$$)AgcLtR>(5e%fZm!S5~AvR}KuxkFRQ~u+=P* zOcL1$bI1t|kMHk3nQk5uC~%JhjktJf1e2&jveE}Q3b^t1GvBm?oniSHEcBQC)B835zgTz7AOvoZvELsOcHMuND{c*yys(&wp*V> zfE}mp=4O5whY_WtY2KVmuGHh4xt-Zgq$D{_X2GU_tAzq;I$-_#yWQ1K$j4s9WjoP! z;f(PWx=KBGlvt~YY*rOnZgoBg+Aic6Nlv7EIklK?OR#OuX5o55B)gE^9zSj!2WUy`p>STwG!IV<)Q!Z%u)Q=rq1yxgqCc zy$?%x1w4n~@#{=5QN29ZySuDpk=!-h7^$H!b%sE|`qd+l4k!P&NUO;%u3(;c4UCb9 z=;6qz%)<=a<27U%<~f5dNiFq0^d>?px5MXpXK1(Drk*QZH;b+`k~{-ZCC-T)akIF0 z09g`SN@~83v>kN3zRi$_$={uUz33qr-fjabGGHH4pr&4C-<63=k>L_Lja61}XZ^9@ z2!|SK=>k^x5L3UEz+^j|c)gu>(Vu}e1N%ea!95*Qp2d24w2=EXeY0DLeDxW>hu;VQ zV|F>W;(Ca;(E5G=>EX{QtUV>Acm4SC-B+O!PojQTeFeZ3#c4Fe)W_|NO|s&6zq>Jl zu*uroMK@W1wwiX5On#{cGvVQM3B&er?jKM{|7CD}H~J|zDFaSPQ4t;l0wKmm zC(WcTO#__%`4gF&ONkvf)|_G0^N-l%(){EBNg5k=a2`5Un!qQaIxcVwfqVvN!rU&)T(-qw|kvP$!f9z;C=4^`A za0-G$2Y7r_t6r<^aFa1C(d2*{8}TR7n24&Xs)%tvWs*`Qq|{qmSl}-$EeX2(dI?5r zwJ@=rz-RcHB!DG7lx}Z2Z&{Za`M0loAE?8-NEaYtf66FG8^8o4Nqswbvl-${p2}ej zrrj>>rXOhZXkA|^adtC5bLBL3=?|r2{2(Z0un_R^)$MY#+AN#04zo;sy1UXFQl16C ztTxyr<>6FcrE8?-omfqOJ;78#K>3~ zc}p~$ct^&uoY7^%1M1zWNw53ro%HJ5WKZTNc6Va;k&gima>F+ia|obG_N6dPr2@9U zuV84HQ~9IrEd@KRY_Mo{b~d}#$yGwSNg~Kb%A~UXhh7Mk$V)%IkmGR1lvqLi(>=^@ z_Vdcj?OdtAt5t`;c76t}=1)PNHU=N3v#NwE-)=MrcXzm|mws%eQ3qVN{jm6g)~G72 zgfZfj>Je38lU=~g>nEw{=QqTW2MkeeXX3mc-rx{24GiNI4w=u8iETJK8P~Je=;^A7 z_@{+W(MlhO=kR%47@aj_NAP`$r8-I=061J5_IqS7ciNw1XH#%T$cV9VzwcP%;FY3| zj(B$9%|B-fcTJ6QeZquPp`f6k-oI5H_Bf@_O%H!Tlpq<$N#p^Z=4%kVa$2-NWZ5Fn zyKw|>d>@w=ooS7&uE0VgnjxF;JrLJEuLXY=ttE8nXicoGrSfA{kBp~usQ2F6#p69X ziB&ty-u^y@ycWVhfk3||A(}X)y#33*Mz(j!UZXKluVk<$+=t)MG2;7l7rEzGL@q5A zY~20Yy;(hGHE?l9ZRkzFFPtp-#y;jh+!ci^oV&$mutI3BtK9Ew=!|>|pJG zwlHO|dWWe6&=F=XAR9@A-}Zd(+2efuW}!!=78w-O0O3$h3R<0&P*@UZOum?Yo1{GV z%YdQCL56XGn~rW4#|B=K*=NqV3X#+V?Wx;TBXLBh{-U}6Me0ji1#M46xyfn)o?dc? zT%u4*Q*B~EufO;VqAYKE)|+7Wmc-axnB04qMf0*GS-)1W?;F?SWL*BCBi7#DPM;@N zwZNDV8NNyGXp{out?!lR#!0K$9hYC|mReT?d{?^umI%3U>|DCI9>*~(mL~9z?u`rQ zB$nhIH~bjT@ptA?<=aVHK5fTUUNH&9M_4qX?qdy<#X^u;W`8%P*nc&)NROLPnt+1n zLx()sy3=4w32N7in~(O!J?oGTIev(@A+)*J{_EVS^PEMUIU6_hccN%O zbs0AvLAKS5h+OmA_&B~Db+r@j>ADnm(>x2?tV5Q!5C5i0{mzo}1!~*Qc1XVetHoXl zYF2bqh)bCgsVf_G$b$>X#9dF3{XWnFYZz;`SPS-!$|R77go!_Wf4us-YV+coad*Pk?_8*N;0Ry=!(sDcqg`K$@ia{^Sn!DN$H_9o%A)oNTNOTdRO@bGogEnsXDS z98{|OAGjR}PIS+%z96-^5$45*p0}aXPv{kmy7dfR!jiqSe%4iRO>7h6D7Jgs#$hjn zIGqYz>kLN7G;MLVbLrA~jtnBenKspfc*zVzOsCktfyLX#Ri02qLpzyaRww~0$es`aj~73js4j|o|Cwg&L} zHbXa@vr>TkG82dqk!la-q~~Q(WdRQ;ece@`6b<~9U%2>5Wb1#{BlL~9wsL3yh1|1d{g5G4dK_2LcBS&6lzXRaVBjH}NXOjV zeEJsEz~?m4f%$Zk!_GfXmJHwhTN=Dq99pwS@qUQ+Ifgg+MWD>;w`y?@Jd%{e(o=h= zW>Ey?e5&yDHKz^4qUU*)Y5DW3{_EGOKG1Yw4k{}{1-Zq^ftg(4l33zz%dEdkMQ_KG zlLSHvg2DicO4obSgzA*FIa?12G)4U@LDWgmJE_bZGVa4<9h;4T*B2);1$!?2&-E>$|IVdu)Ac0o4T#H;@Lf3ca0GC%ym7`-(SJqy{Q2=Ez!!@7)wh_tL|#d zzOJbu3#rsN?{H%--9tH|EY#3(U~xmo%|-Mpvz?79>N(4LT{HNKMe<>{q~1l`L~(V9 zF%&P8u6&e!!Ye;{kUke9+3xn(II8ukAD$?1U+ouSnw5glxzBf$2IkwGD0p7&g zrs}tM@DJF@t@xvf7kwSSkq;$_hbLgMOB^8Z(p=_COzlk^MmNo58E@LI3G1;C=8Yu}s8t zsiJ;0Ux)NsWKzVx$?r*Cnrj~k1K{q*1KmvT_Rpdjn(G89G4w+B z#B?Bfn+SblX<_ODTIxEylZME2j#!hN@ynUU)!kp15TowHw))oKtjn)hFYyuYzwlQ#7p z7&bVMb9m@v^?1!^`;<4InR>LNp65aNs4cU zC*f(7nfz95?%o3&--U`NuaHYOmRL#Ig^sS_C7FtnDRw!f6ceL2F0R{yLk~+2aOrP% zTVcENfv@b>W}SK*KwAMC9``EWQou%#FDG?Gfjt2|*Yl13P&jXZba>wQ@t=#Z=oOe8 zzlU}KQ?rrD$4f_z|jJBfmg>tnwZ?80STGFO+3e$qlSVVx$S>_rFO-SrWSb`1@T*0 zH^a7Msg&D~91GGxnk&Yghf=jwY+Znb;u~LXHhs%sNIWUy*z{fJfoZ|AL51kGOY`i9Q5WJf86sqx%^M*1N5!?&u2 zFg$r#1@%P|2w5*ID)AA=%&`;xK9$0^^_S4_y8yQ;(KKonB7{(U1E+nbiL`SM_r@oB7q~q?-zds( zwS(+>jO2~g6JQ$U-g701p3oN0Uf8+c5a7%vE>yh+=iIu*lf8mA`dfQ*B7T<}c{ zoZJjm);n2+LUvtDZ-YAy%X~9kpJW7h<`^glc?KAWd{O+}Ww=L|mnNqVJs}LIVXO>Q z5|-TC{)jlg_Fgv1$cXnjSm);k3d$?Iwqy8MC%Gp^yH^JKI2%+M=swjYgkpKr=0lx zS8`phwd!mvUILH}BfmKvWm>n{*F4ZjBo5bP_oXss`{!6BF~&meG*CqD>X7R)Hdx{o zOT7$U#Wq>{{Mcs$FvFG3U9L__%4`z0;a5d8069t`~x)GapcO zc~?pUD(M3sKbML2p=*?28)c1dd)7ifIRzF^V@WsuoZ(&J@5MOAF~g>?dk#d~zdWCH z^y*qb{uj72a|kdIkY}KI;h?-?Wodr{Z>CoV-LpVIVk5gJS?LUEiV^mraeU+WGW<8k z>#s|O*DNoKL+15kb3%txer;|=EJFalzQA$tH%oV}$0ULcif$SU zSst=!tRV3$MRyjor3VbDo+!XIl#YoY+-D67A^n_H-#I)D=ln672K}aybowzDfQ&t4 zCQ3O?l`F9=W_OOPiYjb32`OC%y)DA@5bKn7orlJ~F^t4O1*AWJSWJkicS)bJ#knoy zOP~I7IT+w>Toobj8*H`aICP#>J!!h7zjg$j4l44Zm0tDs-iVmGd!)fH z;5e#Bz704rQ5N?QL!a~gA(5LwFW*w+fn>n>c(@eHF!u$n;5+cys%G}*u?13HiADhm z>sf6?ac{FiNK9z>!Zj{{D(L?ZzWVZdld9+J!LUh#&I>H=jV+(X2lqQL(hYH-^C3DW?v=ZTAyB z)j3Riq$2RodiZ_im!o<6tiSz84}Z}nTrQl zf!-Bo|Eg98R|ezb4-eYyXIn#h>Uy9jegXYr5jm zisKTk;CIRFm7ESbC|h26reC6k@1CB1Jik3+Fm8&;u{fXq{ti`BNnpggAJGrO$`2`> zNcr)TCqUbWUza+S|Nc&ou#na63ZWp&|9)!ldSaKd1X0iR#%mh?PoQ^Opv;Ji#0)Ao zF?_OjTjNH(pkUs)@ynsowSFc1UD4kW!K_f*&Yk7czeOj3{50$Nj?_6?V3xBG`k*$?MFLnkTMc-lW)10!@eDYM{A!t5knt>6-_8-M%VjS zO1)8ocso-h$Y=h2BP&E$gOe5*Rg9pzoc$N>>7;NKn46fPo>`GBZ0!?NwAf8%g}_(< zbsVm?13o^a`2ZnWj^fCwp!2lkpx#T;u!5`H>_wYB`rDNAv=&;Sb!-#G0pthHgH6b6 zgHGoq$&a+2!$LD$zmCB2?Dwrcv*_+m8(_d&q(;)nb5X}Tav~PcLzj5c6Jm9+?_1)% zb0>OvKT}`|`1SlN2k;DH3)5}OOql9ZNse_I%4qd`e1DiyJo;uvZ62or=-jqN6hcd^ zD=8Df`6i7^t+#0fv8`v2j3YR9a*i#&iFa;CY|(ziAaasYaZ6;u?&P?}<+(vuMyK-^ z@_dE3>yEY98f11J=n}sg=Jn59RoljJ#J^Q=KS7D71;@XtCNfIhJM-YyFmRzmBu|fj zbfg^zZbp^teP6<9~c)bVq|zh-crsC&vTet?b2#yA!9aN5J7tV8{4o1^Hh2>%^#)_wpC!HLVJ zLsNShUtQ(7oAK;5;~u)T9TA+9oIe(Y40wzaR(Zv5az|eQ-w8YAX;5LO3ZVw9 zc#lOcX46E>_PLzYj5oH&m{USta3fVdotZf4SE%0_pW1AWjt7Ly4VfRi^1qNaW92`% z2Xa4Xop-rMrG`iz?XVpaqL_;t5pQzw+DFdut)B42&z*J5v?CJL8@gSf=7Zi&GK|k< zfZl+u_-ZkO-%|pdNg;0&7wsQqQwFY6Q--g60h%KMzvH%(l-p##ZQa;c)#?2L26qfa zy7V0$Z=bz5OiK>CBZoPMP$qRk>~OQ*#gP%}Sy#nI6gWNI<@IxoD^B=x49z(;*Qhft zr1tXNf1~~UFE!}@gZA^k)S&-D`}tpL(Er2#K}WR}+tTr9rVniLQzxJ LR%(806a9YxIdk8& literal 0 HcmV?d00001 diff --git a/regression/datastore.js b/regression/datastore.js index eb7c50ea656..b2a4bd823b9 100644 --- a/regression/datastore.js +++ b/regression/datastore.js @@ -39,9 +39,9 @@ describe('datastore', function() { ds.save(['Post', postKeyName], post, function(err, key) { if (err) return done(err); assert.equal(key[1], postKeyName); - ds.get(['Post', postKeyName], function(err, key, obj) { + ds.get(['Post', postKeyName], function(err, entity) { if (err) return done(err); - assert.deepEqual(obj, post); + assert.deepEqual(entity.data, post); ds.del(['Post', postKeyName], function(err) { if (err) return done(err); done(); @@ -65,9 +65,9 @@ describe('datastore', function() { ds.save(['Post', postKeyId], post, function(err, key) { if (err) return done(err); assert.equal(key[1], postKeyId); - ds.get(['Post', postKeyId], function(err, key, obj) { + ds.get(['Post', postKeyId], function(err, entity) { if (err) return done(err); - assert.deepEqual(obj, post); + assert.deepEqual(entity.data, post); ds.del(['Post', postKeyId], function(err) { if (err) return done(err); done(); @@ -90,9 +90,9 @@ describe('datastore', function() { if (err) return done(err); assert(key[1]); var assignedId = key[1]; - ds.get(['Post', assignedId], function(err, key, obj) { + ds.get(['Post', assignedId], function(err, entity) { if (err) return done(err); - assert.deepEqual(obj, post); + assert.deepEqual(entity.data, post); ds.del(['Post', assignedId], function(err) { if (err) return done(err); done(); @@ -126,9 +126,9 @@ describe('datastore', function() { assert.equal(keys.length,2); var firstKey = ['Post', keys[0][1]], secondKey = ['Post', keys[1][1]]; - ds.getAll([firstKey, secondKey], function(err, keys, objs) { + ds.getAll([firstKey, secondKey], function(err, entities) { if (err) return done(err); - assert.equal(objs.length, 2); + assert.equal(entities.length, 2); ds.delAll([firstKey, secondKey], function(err) { if (err) return done(err); done(); @@ -205,17 +205,17 @@ describe('datastore', function() { it('should limit queries', function(done) { var q = ds.createQuery('Character').limit(5); - ds.runQuery(q, function(err, keys, objs, secondQuery) { + ds.runQuery(q, function(err, firstEntities, secondQuery) { if (err) return done(err); - assert.equal(objs.length, 5); + assert.equal(firstEntities.length, 5); assert(secondQuery); - ds.runQuery(secondQuery, function(err, keys, objs, thirdQuery) { + ds.runQuery(secondQuery, function(err, secondEntities, thirdQuery) { if (err) return done(err); - assert.equal(objs.length, 3); + assert.equal(secondEntities.length, 3); // TODO(silvano): it currently requires an additional request that brings // an empty page and a null query //assert.equal(thirdQuery, null) - ds.runQuery(thirdQuery, function(err, keys, objs, fourthQuery) { + ds.runQuery(thirdQuery, function(err, thirdEntities, fourthQuery) { if (err) return done(err); assert.equal(fourthQuery, null); done(); @@ -227,9 +227,9 @@ describe('datastore', function() { it('should filter queries with simple indexes', function(done) { var q = ds.createQuery('Character') .filter('appearances >=', 20); - ds.runQuery(q, function(err, keys, objs, nextQuery) { + ds.runQuery(q, function(err, entities, nextQuery) { if (err) return done(err); - assert.equal(objs.length, 6); + assert.equal(entities.length, 6); done(); }); }); @@ -238,28 +238,38 @@ describe('datastore', function() { var q = ds.createQuery('Character') .filter('family =', 'Stark') .filter('appearances >=', 20); - ds.runQuery(q, function(err, keys, objs, nextQuery) { + ds.runQuery(q, function(err, entities, nextQuery) { if (err) return done(err); - assert.equal(objs.length, 6); + assert.equal(entities.length, 6); done(); }); }); it('should filter by ancestor', function(done) { var q = ds.createQuery('Character').hasAncestor(['Character', 'Eddard']); - ds.runQuery(q, function(err, keys, objs, nextQuery) { + ds.runQuery(q, function(err, entities, nextQuery) { if (err) return done(err); - assert.equal(objs.length, 5); + assert.equal(entities.length, 5); + done(); + }); + }); + + it('should filter by key', function(done) { + var q = ds.createQuery('Character') + .filter('__key__ =', ['Character', 'Rickard']); + ds.runQuery(q, function(err, entities, nextQuery) { + if (err) return done(err); + assert.equal(entities.length, 1); done(); }); }); it('should order queries', function(done) { var q = ds.createQuery('Character').order('+appearances'); - ds.runQuery(q, function(err, keys, objs, nextQuery) { + ds.runQuery(q, function(err, entities, nextQuery) { if (err) return done(err); - assert.equal(objs[0].name, characters[0].name); - assert.equal(objs[7].name, characters[3].name); + assert.equal(entities[0].data.name, characters[0].name); + assert.equal(entities[7].data.name, characters[3].name); done(); }); }); @@ -267,13 +277,13 @@ describe('datastore', function() { it('should select projections', function(done) { var q = ds.createQuery('Character') .select(['name', 'family']); - ds.runQuery(q, function(err, keys, objs, nextQuery) { + ds.runQuery(q, function(err, entities, nextQuery) { if (err) return done(err); - assert.deepEqual(objs[0], { + assert.deepEqual(entities[0].data, { name: 'Arya', family: 'Stark' }); - assert.deepEqual(objs[8], { + assert.deepEqual(entities[8].data, { name: 'Sansa', family: 'Stark' }); @@ -286,15 +296,15 @@ describe('datastore', function() { .offset(2) .limit(3) .order('+appearances'); - ds.runQuery(q, function(err, keys, objs, secondQuery) { + ds.runQuery(q, function(err, entities, secondQuery) { if (err) return done(err); - assert.equal(objs.length, 3); - assert.equal(objs[0].name, 'Robb'); - assert.equal(objs[2].name, 'Catelyn'); - ds.runQuery(secondQuery, function(err, keys, objs, thirdQuery) { - assert.equal(objs.length, 3); - assert.equal(objs[0].name, 'Sansa'); - assert.equal(objs[2].name, 'Arya'); + assert.equal(entities.length, 3); + assert.equal(entities[0].data.name, 'Robb'); + assert.equal(entities[2].data.name, 'Catelyn'); + ds.runQuery(secondQuery, function(err, secondEntities, thirdQuery) { + assert.equal(secondEntities.length, 3); + assert.equal(secondEntities[0].data.name, 'Sansa'); + assert.equal(secondEntities[2].data.name, 'Arya'); done(); }); }); @@ -305,17 +315,17 @@ describe('datastore', function() { .offset(2) .limit(2) .order('+appearances'); - ds.runQuery(q, function(err, keys, objs, nextQuery) { + ds.runQuery(q, function(err, entities, nextQuery) { if (err) return done(err); var startCursor = nextQuery.startVal; var cursorQuery = ds.createQuery('Character') .order('+appearances') .start(startCursor); - ds.runQuery(cursorQuery, function(err, keys, objs, nextQuery) { + ds.runQuery(cursorQuery, function(err, secondEntities, nextQuery) { if (err) return done(err); - assert.equal(objs.length, 4); - assert.equal(objs[0].name, 'Catelyn'); - assert.equal(objs[3].name, 'Arya'); + assert.equal(secondEntities.length, 4); + assert.equal(secondEntities[0].data.name, 'Catelyn'); + assert.equal(secondEntities[3].data.name, 'Arya'); done(); }); }); @@ -324,9 +334,9 @@ describe('datastore', function() { it('should group queries', function(done) { var q = ds.createQuery('Character') .groupBy('alive'); - ds.runQuery(q, function(err, keys, objs, nextQuery) { + ds.runQuery(q, function(err, entities, nextQuery) { if (err) return done(err); - assert.equal(objs.length, 2); + assert.equal(entities.length, 2); done(); }); }) @@ -350,9 +360,9 @@ describe('datastore', function() { 'url': 'www.google.com' }; ds.runInTransaction(function(t, tDone) { - ds.get(key, function(err, keyRes, objRes) { + ds.get(key, function(err, entity) { if (err) return done(err); - if (objRes) { + if (entity) { tDone(); return; } else { @@ -365,10 +375,10 @@ describe('datastore', function() { }); }, function(err) { if (err) throw (err); - ds.get(key, function(err, keyRes, objRes) { + ds.get(key, function(err, entity) { if (err) return done(err); - assert.deepEqual(objRes, obj); - ds.del(keyRes, function(err) { + assert.deepEqual(entity.data, obj); + ds.del(entity.key, function(err) { if (err) return done(err); done(); }) diff --git a/regression/env.js b/regression/env.js index df20e9ec2fd..afc024952cb 100644 --- a/regression/env.js +++ b/regression/env.js @@ -15,6 +15,7 @@ */ if (!process.env.GCLOUD_TESTS_PROJECT_ID && + !process.env.GCLOUD_TESTS_BUCKET_NAME && !process.env.GCLOUD_TESTS_KEY) { var error = ['To run the regression tests, you need to set the value of some environment variables.', 'Please check the README for instructions.' @@ -24,5 +25,6 @@ if (!process.env.GCLOUD_TESTS_PROJECT_ID && module.exports = { projectId: process.env.GCLOUD_TESTS_PROJECT_ID, - keyFilename: process.env.GCLOUD_TESTS_KEY + bucketName: process.env.GCLOUD_TESTS_BUCKET_NAME, + keyFilename: process.env.GCLOUD_TESTS_KEY, }; diff --git a/regression/storage.js b/regression/storage.js new file mode 100644 index 00000000000..1f8413d73fc --- /dev/null +++ b/regression/storage.js @@ -0,0 +1,173 @@ +/** + * 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. + */ + +var assert = require('assert'), + fs = require('fs'); + +var env = require('./env.js'), + gcloud = require('../lib'), + bucket = new gcloud.storage.Bucket(env); + +var pathToLogoFile = 'regression/data/CloudPlatform_128px_Retina.png'; +var pathToReadFile = 'regression/data/readfile.txt'; + +describe('storage', function() { + + describe('write, read and remove files', function() { + + it('should write/remove from file', function(done) { + var fileName = 'CloudLogo'; + bucket.write(fileName, { filename: pathToLogoFile }, function(err, fileObject, resp) { + if (err) { return done(err); } + assert(fileObject); + bucket.remove(fileName, function(err) { + if (err) { return done(err); } + done(); + }); + }); + }); + + it('should write/remove from stream', function(done) { + var fileName = 'CloudLogo'; + bucket.write(fileName, { data: fs.createReadStream(pathToLogoFile) }, + function(err, fileObject) { + if (err) { return done(err); } + assert(fileObject); + bucket.remove(fileName, function(err) { + if (err) { return done(err); } + done(); + }); + }); + }); + + it('should write/read/remove from a buffer', function(done) { + var fileName = 'MyBuffer', + fileContent = 'Hello World'; + bucket.write(fileName, { data: fileContent }, function(err, fileObject) { + if (err) { return done(err); } + assert(fileObject); + var content = ''; + bucket.createReadStream(fileName) + .pipe(fs.createWriteStream(pathToReadFile)) + .on('error', done) + .on('complete', function(content) { + bucket.remove(fileName, function(err) { + if (err) { return done(err); } + fs.readFile(pathToReadFile, function(err, data) { + assert.equal(data, fileContent); + fs.unlink(pathToReadFile, function(err) { + if (err) { return done(err); } + done(); + }); + }); + }); + }); + }); + }); + + it('should write and read metadata', function(done) { + var fileName = 'CloudLogo', + myMetadata = { contentType: 'image/png'}; + bucket.write(fileName, { filename: pathToLogoFile, metadata: myMetadata }, + function(err, fileObject) { + if (err) { return done(err); } + bucket.stat(fileName, function(err, metadata) { + if (err) { return done(err); } + assert.equal(metadata['contentType'], myMetadata['contentType']); + bucket.remove(fileName, function(err) { + if (err) { return done(err); } + done(); + }); + }); + }); + }); + + it('should copy an existing file', function(done) { + var fileName = 'CloudLogo', + copyName = 'CloudLogoCopy'; + + bucket.write(fileName, { filename: pathToLogoFile }, function(err, fileObject) { + if (err) { return done(err); } + bucket.copy(fileName, { name: copyName }, function() { + if (err) { return done(err); } + bucket.remove(copyName, function(err) { + if (err) { return done(err); } + bucket.remove(fileName, function(err) { + if (err) { return done(err); } + done(); + }); + }); + }); + }); + }); + + }); + + describe('list files', function() { + + var filenames = ['CloudLogo1', 'CloudLogo2', 'CloudLogo3']; + + before(function(done) { + bucket.write(filenames[0], { filename: pathToLogoFile }, function(err, fileObject) { + if (err) { return done(err); } + bucket.copy(filenames[0], { name: filenames[1] }, function() { + if (err) { return done(err); } + bucket.copy(filenames[0], { name: filenames[2] }, function() { + if (err) { return done(err); } + done(); + }); + }); + }); + }); + + it('should list files', function(done) { + bucket.list(function(err, files, nextQuery) { + if (err) { return done(err); } + assert.equal(files.length, 3); + assert.equal(nextQuery, null); + done(); + }); + }); + + it('should paginate the list', function(done) { + bucket.list({ maxResults: 2 }, function(err, files, nextQuery) { + if (err) { return done(err); } + assert.equal(files.length, 2); + assert(nextQuery); + bucket.list(nextQuery, function(err, files, nextQuery) { + if (err) { return done(err); } + assert(files); + done(); + }); + }); + }); + + after(function(done) { + bucket.remove(filenames[0], function(err) { + if (err) { return done(err); } + bucket.remove(filenames[1], function(err) { + if (err) { return done(err); } + bucket.remove(filenames[2], function() { + if (err) { return done(err); } + done(); + }); + }); + }); + }); + + }); + +}); diff --git a/test/common.util.js b/test/common.util.js index cb86b6e0d6c..73af4c68b7e 100644 --- a/test/common.util.js +++ b/test/common.util.js @@ -32,4 +32,45 @@ describe('arrayize', function() { assert.deepEqual(o, ['text']); done(); }); -}); \ No newline at end of file +}); + +describe('handleResp', function() { + + it('should handle errors', function(done) { + var defaultErr = new Error('new error'); + util.handleResp(defaultErr, null, null, function(err) { + assert.equal(err, defaultErr); + done(); + }); + }); + + it('should handle body errors', function(done) { + var apiErr = { + errors: [{ foo: 'bar' }], + code: 400, + message: 'an error occurred' + } + util.handleResp(null, {}, { error: apiErr }, function(err) { + assert.deepEqual(err.errors, apiErr.errors); + assert.strictEqual(err.code, apiErr.code); + assert.deepEqual(err.message, apiErr.message); + done(); + }); + }); + + it('should try to parse JSON if body is string', function(done) { + var body = '{ "foo": "bar" }'; + util.handleResp(null, {}, body, function(err, body) { + assert.strictEqual(body.foo, 'bar'); + done(); + }); + }); + + it('should return status code as an error if there are not other errors', function(done) { + util.handleResp(null, { statusCode: 400 }, null, function(err) { + assert.strictEqual(err.message, 'error during request, statusCode: 400'); + done(); + }); + }); + +}); diff --git a/test/datastore.js b/test/datastore.js index 444b71d9ef4..5e496979b45 100644 --- a/test/datastore.js +++ b/test/datastore.js @@ -89,13 +89,14 @@ describe('Dataset', function() { assert.equal(proto.keys.length, 1); callback(null, mockResp_get); }; - ds.get(['Kind', 123], function(err, key, obj) { - assert.deepEqual(key, ['Kind', 5732568548769792]); - assert.strictEqual(obj.name, 'Burcu'); - assert.deepEqual(obj.bytes, new Buffer('hello')); - assert.strictEqual(obj.done, false); - assert.deepEqual(obj.total, 6.7); - assert.strictEqual(obj.createdat.getTime(), 978307200000); + ds.get(['Kind', 123], function(err, entity) { + var properties = entity.data; + assert.deepEqual(entity.key, ['Kind', 5732568548769792]); + assert.strictEqual(properties.name, 'Burcu'); + assert.deepEqual(properties.bytes, new Buffer('hello')); + assert.strictEqual(properties.done, false); + assert.deepEqual(properties.total, 6.7); + assert.strictEqual(properties.createdat.getTime(), 978307200000); done(); }); }); @@ -108,13 +109,15 @@ describe('Dataset', function() { callback(null, mockResp_get); }; ds.getAll([ - ['Kind', 123]], function(err, keys, objs) { - assert.deepEqual(keys[0], ['Kind', 5732568548769792]); - assert.strictEqual(objs[0].name, 'Burcu'); - assert.deepEqual(objs[0].bytes, new Buffer('hello')); - assert.strictEqual(objs[0].done, false); - assert.deepEqual(objs[0].total, 6.7); - assert.strictEqual(objs[0].createdat.getTime(), 978307200000); + ['Kind', 123]], function(err, entities) { + var entity = entities[0]; + var properties = entity.data; + assert.deepEqual(entity.key, ['Kind', 5732568548769792]); + assert.strictEqual(properties.name, 'Burcu'); + assert.deepEqual(properties.bytes, new Buffer('hello')); + assert.strictEqual(properties.done, false); + assert.deepEqual(properties.total, 6.7); + assert.strictEqual(properties.createdat.getTime(), 978307200000); done(); }); }); @@ -197,4 +200,77 @@ describe('Dataset', function() { }); }); + describe('runQuery', function() { + var ds; + var query; + var mockResponse = { + withResults: { + batch: { entityResults: mockResp_get.found } + }, + withResultsAndEndCursor: { + batch: { entityResults: mockResp_get.found, endCursor: 'cursor' } + }, + withoutResults: mockResp_get + }; + + beforeEach(function () { + ds = new datastore.Dataset({ projectId: 'test' }); + query = ds.createQuery('Kind'); + }); + + describe('errors', function() { + it('should handle upstream errors', function() { + var upstreamError = new Error('upstream error.'); + ds.transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'runQuery'); + callback(upstreamError); + }; + + ds.runQuery(query, function(err) { + assert.equal(err, upstreamError); + }); + }); + + it('should handle missing results error', function() { + ds.transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'runQuery'); + callback('simulated-error', mockResponse.withoutResults); + }; + + ds.runQuery(query, function(err) { + assert.equal(err, 'simulated-error'); + }); + }); + }); + + it('should execute callback with results', function() { + ds.transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'runQuery'); + callback(null, mockResponse.withResults); + }; + + ds.runQuery(query, function (err, entities) { + assert.ifError(err); + + var properties = entities[0].data; + assert.deepEqual(entities[0].key, ['Kind', 5732568548769792]); + assert.strictEqual(properties.name, 'Burcu'); + assert.deepEqual(properties.bytes, new Buffer('hello')); + assert.strictEqual(properties.done, false); + assert.deepEqual(properties.total, 6.7); + }); + }); + + it('should return a new query if results remain', function() { + ds.transaction.makeReq = function(method, proto, callback) { + assert.equal(method, 'runQuery'); + callback(null, mockResponse.withResultsAndEndCursor); + }; + + ds.runQuery(query, function(err, entities, nextQuery) { + assert.ifError(err); + assert.equal(nextQuery.constructor.name, 'Query'); + }); + }); + }); });