diff --git a/src/dataset.js b/src/dataset.js index 3c3f0efc..c80cc5fb 100644 --- a/src/dataset.js +++ b/src/dataset.js @@ -30,13 +30,22 @@ var Table = require('./table.js'); * @class * @param {BigQuery} bigQuery {@link BigQuery} instance. * @param {string} id The ID of the Dataset. + * @param {object} [options] Dataset options. + * @param {string} [options.location] The geographic location of the dataset. + * Defaults to US. * * @example * const BigQuery = require('@google-cloud/bigquery'); * const bigquery = new BigQuery(); * const dataset = bigquery.dataset('institutions'); */ -function Dataset(bigQuery, id) { +function Dataset(bigQuery, id, options) { + var self = this; + + if (options && options.location) { + this.location = options.location; + } + var methods = { /** * Create a dataset. @@ -199,8 +208,16 @@ function Dataset(bigQuery, id) { parent: bigQuery, baseUrl: '/datasets', id: id, - createMethod: bigQuery.createDataset.bind(bigQuery), methods: methods, + createMethod: function(id, options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = extend({}, options, {location: self.location}); + return bigQuery.createDataset(id, options, callback); + }, }); this.bigQuery = bigQuery; @@ -244,6 +261,7 @@ Dataset.prototype.createQueryJob = function(options, callback) { defaultDataset: { datasetId: this.id, }, + location: this.location, }); return this.bigQuery.createQueryJob(options, callback); @@ -270,6 +288,7 @@ Dataset.prototype.createQueryStream = function(options) { defaultDataset: { datasetId: this.id, }, + location: this.location, }); return this.bigQuery.createQueryStream(options); @@ -345,9 +364,11 @@ Dataset.prototype.createTable = function(id, options, callback) { return; } - var table = self.table(resp.tableReference.tableId); - table.metadata = resp; + var table = self.table(resp.tableReference.tableId, { + location: resp.location, + }); + table.metadata = resp; callback(null, table, resp); } ); @@ -484,7 +505,10 @@ Dataset.prototype.getTables = function(options, callback) { } var tables = (resp.tables || []).map(function(tableObject) { - var table = that.table(tableObject.tableReference.tableId); + var table = that.table(tableObject.tableReference.tableId, { + location: tableObject.location, + }); + table.metadata = tableObject; return table; }); @@ -545,6 +569,7 @@ Dataset.prototype.query = function(options, callback) { defaultDataset: { datasetId: this.id, }, + location: this.location, }); return this.bigQuery.query(options, callback); @@ -554,6 +579,13 @@ Dataset.prototype.query = function(options, callback) { * Create a Table object. * * @param {string} id The ID of the table. + * @param {object} [options] Table options. + * @param {string} [options.location] The geographic location of the table, by + * default this value is inherited from the dataset. This can be used to + * configure the location of all jobs created through a table instance. It + * cannot be used to set the actual location of the table. This value will + * be superseded by any API responses containing location data for the + * table. * @return {Table} * * @example @@ -563,8 +595,15 @@ Dataset.prototype.query = function(options, callback) { * * const institutions = dataset.table('institution_data'); */ -Dataset.prototype.table = function(id) { - return new Table(this, id); +Dataset.prototype.table = function(id, options) { + options = extend( + { + location: this.location, + }, + options + ); + + return new Table(this, id, options); }; /*! Developer Documentation diff --git a/src/index.js b/src/index.js index 37d7e2f2..3b0d8ab9 100644 --- a/src/index.js +++ b/src/index.js @@ -53,6 +53,8 @@ var Table = require('./table.js'); * attempted before returning the error. * @property {Constructor} [promise] Custom promise module to use instead of * native Promises. + * @property {string} [location] The geographic location of all datasets and + * jobs referenced and created through the client. */ /** @@ -102,6 +104,12 @@ function BigQuery(options) { }; common.Service.call(this, config, options); + + /** + * @name BigQuery#location + * @type {string} + */ + this.location = options.location; } util.inherits(BigQuery, common.Service); @@ -617,11 +625,18 @@ BigQuery.prototype.createDataset = function(id, options, callback) { { method: 'POST', uri: '/datasets', - json: extend(true, {}, options, { - datasetReference: { - datasetId: id, + json: extend( + true, + { + location: this.location, }, - }), + options, + { + datasetReference: { + datasetId: id, + }, + } + ), }, function(err, resp) { if (err) { @@ -654,6 +669,8 @@ BigQuery.prototype.createDataset = function(id, options, callback) { * @param {boolean} [options.dryRun] If set, don't actually run this job. A * valid query will update the job with processing statistics. These can be * accessed via `job.metadata`. + * @param {string} [options.location] The geographic location of the job. + * Required except for US and EU. * @param {string} [options.jobId] Custom job id. * @param {string} [options.jobPrefix] Prefix to apply to the job id. * @param {string} options.query A query string, following the BigQuery query @@ -779,6 +796,11 @@ BigQuery.prototype.createQueryJob = function(options, callback) { delete query.jobPrefix; } + if (query.location) { + reqOpts.location = query.location; + delete query.location; + } + if (query.jobId) { reqOpts.jobId = query.jobId; delete query.jobId; @@ -840,6 +862,8 @@ BigQuery.prototype.createQueryStream = common.paginator.streamify('query'); * @param {object} options Object in the form of a [Job resource](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs); * @param {string} [options.jobId] Custom job id. * @param {string} [options.jobPrefix] Prefix to apply to the job id. + * @param {string} [options.location] The geographic location of the job. + * Required except for US and EU. * @param {function} [callback] The callback function. * @param {?error} callback.err An error returned while making this request. * @param {Job} callback.job The newly created job. @@ -893,8 +917,14 @@ BigQuery.prototype.createJob = function(options, callback) { reqOpts.jobReference = { projectId: this.projectId, jobId: jobId, + location: this.location, }; + if (options.location) { + reqOpts.jobReference.location = options.location; + delete reqOpts.location; + } + this.request( { method: 'POST', @@ -907,10 +937,19 @@ BigQuery.prototype.createJob = function(options, callback) { return; } - var job = self.job(jobId); - job.metadata = resp; + if (resp.status.errors) { + err = new common.util.ApiError({ + errors: resp.status.errors, + response: resp, + }); + } + + var job = self.job(jobId, { + location: resp.jobReference.location, + }); - callback(null, job, resp); + job.metadata = resp; + callback(err, job, resp); } ); }; @@ -919,6 +958,9 @@ BigQuery.prototype.createJob = function(options, callback) { * Create a reference to a dataset. * * @param {string} id ID of the dataset. + * @param {object} [options] Dataset options. + * @param {string} [options.location] The geographic location of the dataset. + * Required except for US and EU. * @returns {Dataset} * * @example @@ -926,8 +968,12 @@ BigQuery.prototype.createJob = function(options, callback) { * const bigquery = new BigQuery(); * const dataset = bigquery.dataset('higher_education'); */ -BigQuery.prototype.dataset = function(id) { - return new Dataset(this, id); +BigQuery.prototype.dataset = function(id, options) { + if (this.location) { + options = extend({location: this.location}, options); + } + + return new Dataset(this, id, options); }; /** @@ -1008,7 +1054,10 @@ BigQuery.prototype.getDatasets = function(options, callback) { } var datasets = (resp.datasets || []).map(function(dataset) { - var ds = that.dataset(dataset.datasetReference.datasetId); + var ds = that.dataset(dataset.datasetReference.datasetId, { + location: dataset.location, + }); + ds.metadata = dataset; return ds; }); @@ -1141,7 +1190,10 @@ BigQuery.prototype.getJobs = function(options, callback) { } var jobs = (resp.jobs || []).map(function(jobObject) { - var job = that.job(jobObject.jobReference.jobId); + var job = that.job(jobObject.jobReference.jobId, { + location: jobObject.jobReference.location, + }); + job.metadata = jobObject; return job; }); @@ -1187,6 +1239,9 @@ BigQuery.prototype.getJobsStream = common.paginator.streamify('getJobs'); * Create a reference to an existing job. * * @param {string} id ID of the job. + * @param {object} [options] Configuration object. + * @param {string} [options.location] The geographic location of the job. + * Required except for US and EU. * @returns {Job} * * @example @@ -1195,8 +1250,12 @@ BigQuery.prototype.getJobsStream = common.paginator.streamify('getJobs'); * * const myExistingJob = bigquery.job('job-id'); */ -BigQuery.prototype.job = function(id) { - return new Job(this, id); +BigQuery.prototype.job = function(id, options) { + if (this.location) { + options = extend({location: this.location}, options); + } + + return new Job(this, id, options); }; /** @@ -1208,6 +1267,8 @@ BigQuery.prototype.job = function(id) { * @param {string|object} query A string SQL query or configuration object. * For all available options, see * [Jobs: query request body](https://cloud.google.com/bigquery/docs/reference/v2/jobs/query#request-body). + * @param {string} [query.location] The geographic location of the job. + * Required except for US and EU. * @param {string} [query.jobId] Custom id for the underlying job. * @param {string} [query.jobPrefix] Prefix to apply to the underlying job id. * @param {object|Array<*>} query.params For positional SQL parameters, provide diff --git a/src/job.js b/src/job.js index 30661478..6c66bb49 100644 --- a/src/job.js +++ b/src/job.js @@ -43,6 +43,9 @@ var util = require('util'); * @class * @param {BigQuery} bigQuery {@link BigQuery} instance. * @param {string} id The ID of the job. + * @param {object} [options] Configuration object. + * @param {string} [options.location] The geographic location of the job. + * Required except for US and EU. * * @example * const BigQuery = require('@google-cloud/bigquery'); @@ -74,7 +77,11 @@ var util = require('util'); * //- * job.removeAllListeners(); */ -function Job(bigQuery, id) { +function Job(bigQuery, id, options) { + if (options && options.location) { + this.location = options.location; + } + var methods = { /** * Check if the job exists. @@ -141,7 +148,6 @@ function Job(bigQuery, id) { * * @see [Jobs: get API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/get} * - * @method Job#getMetadata * @param {function} [callback] The callback function. * @param {?error} callback.err An error returned while making this * request. @@ -164,7 +170,11 @@ function Job(bigQuery, id) { * const apiResponse = data[1]; * }); */ - getMetadata: true, + getMetadata: { + reqOpts: { + qs: {location: this.location}, + }, + }, /** * Set the metadata for this job. This can be useful for updating job @@ -214,18 +224,6 @@ function Job(bigQuery, id) { }); this.bigQuery = bigQuery; - - // The API endpoint for cancel is: .../bigquery/v2/project/projectId/... - // The rest of the API endpoints are: .../bigquery/v2/projects/projectId/... - // Reference: https://github.com/GoogleCloudPlatform/google-cloud-node/issues/1027 - this.interceptors.push({ - request: function(reqOpts) { - if (reqOpts.uri.indexOf('/cancel') > -1) { - reqOpts.uri = reqOpts.uri.replace('/projects/', '/project/'); - } - return reqOpts; - }, - }); } util.inherits(Job, common.Operation); @@ -261,16 +259,19 @@ util.inherits(Job, common.Operation); * }); */ Job.prototype.cancel = function(callback) { - callback = callback || common.util.noop; + var qs; + + if (this.location) { + qs = {location: this.location}; + } this.request( { method: 'POST', uri: '/cancel', + qs, }, - function(err, resp) { - callback(err, resp); - } + callback ); }; @@ -364,6 +365,13 @@ Job.prototype.getQueryResults = function(options, callback) { options = {}; } + options = extend( + { + location: this.location, + }, + options + ); + this.bigQuery.request( { uri: '/queries/' + this.id, diff --git a/src/table.js b/src/table.js index ee5090df..b3e12b6a 100644 --- a/src/table.js +++ b/src/table.js @@ -48,6 +48,13 @@ var FORMATS = { * @class * @param {Dataset} dataset {@link Dataset} instance. * @param {string} id The ID of the table. + * @param {object} [options] Table options. + * @param {string} [options.location] The geographic location of the table, by + * default this value is inherited from the dataset. This can be used to + * configure the location of all jobs created through a table instance. It + * cannot be used to set the actual location of the table. This value will + * be superseded by any API responses containing location data for the + * table. * * @example * const BigQuery = require('@google-cloud/bigquery'); @@ -56,7 +63,11 @@ var FORMATS = { * * const table = dataset.table('my-table'); */ -function Table(dataset, id) { +function Table(dataset, id, options) { + if (options && options.location) { + this.location = options.location; + } + var methods = { /** * Create a table. @@ -623,6 +634,10 @@ Table.prototype.createCopyJob = function(destination, metadata, callback) { delete metadata.jobPrefix; } + if (this.location) { + body.location = this.location; + } + if (metadata.jobId) { body.jobId = metadata.jobId; delete metadata.jobId; @@ -727,6 +742,10 @@ Table.prototype.createCopyFromJob = function(sourceTables, metadata, callback) { delete metadata.jobPrefix; } + if (this.location) { + body.location = this.location; + } + if (metadata.jobId) { body.jobId = metadata.jobId; delete metadata.jobId; @@ -868,6 +887,10 @@ Table.prototype.createExtractJob = function(destination, options, callback) { delete options.jobPrefix; } + if (this.location) { + body.location = this.location; + } + if (options.jobId) { body.jobId = options.jobId; delete options.jobId; @@ -972,6 +995,10 @@ Table.prototype.createLoadJob = function(source, metadata, callback) { delete metadata.format; } + if (this.location) { + metadata.location = this.location; + } + if (is.string(source)) { // A path to a file was given. If a sourceFormat wasn't specified, try to // find a match from the file's extension. @@ -1013,6 +1040,11 @@ Table.prototype.createLoadJob = function(source, metadata, callback) { delete metadata.jobPrefix; } + if (metadata.location) { + body.location = metadata.location; + delete metadata.location; + } + if (metadata.jobId) { body.jobId = metadata.jobId; delete metadata.jobId; @@ -1213,6 +1245,7 @@ Table.prototype.createWriteStream = function(metadata) { jobReference: { jobId: jobId, projectId: self.bigQuery.projectId, + location: self.location, }, }, request: { @@ -1223,9 +1256,11 @@ Table.prototype.createWriteStream = function(metadata) { }, }, function(data) { - var job = self.bigQuery.job(data.jobReference.jobId); - job.metadata = data; + var job = self.bigQuery.job(data.jobReference.jobId, { + location: data.jobReference.location, + }); + job.metadata = data; dup.emit('complete', job); } ); diff --git a/system-test/bigquery.js b/system-test/bigquery.js index 7efe3c50..4d5b0ec7 100644 --- a/system-test/bigquery.js +++ b/system-test/bigquery.js @@ -532,6 +532,184 @@ describe('BigQuery', function() { } ); }); + + describe('location', function() { + var LOCATION = 'asia-northeast1'; + + var dataset = bigquery.dataset(generateName('dataset'), { + location: LOCATION, + }); + + var table = dataset.table(generateName('table')); + var job; + + var QUERY = `SELECT * FROM \`${table.id}\``; + var SCHEMA = require('./data/schema.json'); + var TEST_DATA_FILE = require.resolve('./data/location-test-data.json'); + + before(function() { + // create a dataset in a certain location will cascade the location + // to any jobs created through it + return dataset + .create() + .then(function() { + return table.create({schema: SCHEMA}); + }) + .then(function() { + return table.createLoadJob(TEST_DATA_FILE); + }) + .then(function(data) { + job = data[0]; + return job.promise(); + }); + }); + + it('should create a load job in the correct location', function() { + assert.strictEqual(job.location, LOCATION); + }); + + describe('job.get', function() { + it('should fail to reload if the location is not set', function(done) { + var badJob = bigquery.job(job.id); + + badJob.getMetadata(function(err) { + assert.strictEqual(err.code, 404); + done(); + }); + }); + + it('should fail to reload if the location is wrong', function(done) { + var badJob = bigquery.job(job.id, {location: 'US'}); + + badJob.getMetadata(function(err) { + assert.strictEqual(err.code, 404); + done(); + }); + }); + + it('should reload if the location matches', function(done) { + var goodJob = bigquery.job(job.id, {location: LOCATION}); + + goodJob.getMetadata(function(err) { + assert.ifError(err); + assert.strictEqual(goodJob.location, LOCATION); + done(); + }); + }); + }); + + describe('job.cancel', function() { + var job; + + before(function() { + return dataset.createQueryJob(QUERY).then(function(data) { + job = data[0]; + }); + }); + + it('should fail if the job location is incorrect', function(done) { + var badJob = bigquery.job(job.id, {location: 'US'}); + + badJob.cancel(function(err) { + assert.strictEqual(err.code, 404); + done(); + }); + }); + + it('should cancel a job', function(done) { + job.cancel(done); + }); + }); + + describe('job.getQueryResults', function() { + it('should fail if the job location is incorrect', function(done) { + var badDataset = bigquery.dataset(dataset.id, {location: 'US'}); + + badDataset.createQueryJob( + { + query: QUERY, + }, + function(err, job) { + assert.strictEqual(err.errors[0].reason, 'notFound'); + assert.strictEqual(job.location, 'US'); + done(); + } + ); + }); + + it('should get query results', function() { + var job; + + return dataset + .createQueryJob(QUERY) + .then(function(data) { + job = data[0]; + + assert.strictEqual(job.location, LOCATION); + return job.promise(); + }) + .then(function() { + return job.getQueryResults(); + }) + .then(function(data) { + var rows = data[0]; + + assert(rows.length > 0); + }); + }); + }); + + describe('job.insert', function() { + describe('copy', function() { + var otherTable = dataset.table(generateName('table')); + + it('should fail if the job location is incorrect', function(done) { + var badTable = dataset.table(table.id, {location: 'US'}); + + badTable.createCopyJob(otherTable, function(err) { + assert.strictEqual(err.code, 404); + done(); + }); + }); + + it('should copy the table', function() { + return table.createCopyJob(otherTable).then(function(data) { + var job = data[0]; + + assert.strictEqual(job.location, LOCATION); + return job.promise(); + }); + }); + }); + + describe('extract', function() { + var bucket = storage.bucket(generateName('bucket')); + var extractFile = bucket.file('location-extract-data.json'); + + before(function() { + return bucket.create({location: LOCATION}); + }); + + it('should fail if the job location is incorrect', function(done) { + var badTable = dataset.table(table.id, {location: 'US'}); + + badTable.createExtractJob(extractFile, function(err) { + assert.strictEqual(err.code, 404); + done(); + }); + }); + + it('should extract the table', function() { + return table.createExtractJob(extractFile).then(function(data) { + var job = data[0]; + + assert.strictEqual(job.location, LOCATION); + return job.promise(); + }); + }); + }); + }); + }); }); describe('BigQuery/Table', function() { diff --git a/system-test/data/location-test-data.json b/system-test/data/location-test-data.json new file mode 100644 index 00000000..3604deb3 --- /dev/null +++ b/system-test/data/location-test-data.json @@ -0,0 +1,3 @@ +{"Age":"111","FavoriteTime":"2031-04-01T05:09:27","IsMagic":false,"Name":"Bilbo","NextVacation":"2017-09-22","Spells":[],"TeaTime":"10:00:00","Weight":67.2} +{"Age":"1000","FavoriteTime":"2001-12-19T23:59:59","IsMagic":true,"Name":"Gandalf","NextVacation":"2666-06-06","Spells":[{"DiscoveredBy":"Firebreather","Icon":"iVBORw0KGgoAAAANSUhEUgAAAB4AAAAgCAYAAAAFQMh/AAAAAXNSR0IArs4c6QAAA9lJREFUSA21lk9OVEEQxvsRDImoiMG9mLjjCG5mEg7gEfQGsIcF7p0EDsBBSJiNO7ZsFRZqosb/QkSj7fer7ur33sw8GDFUUq+7q6vqq6qu7pkQzqG4EeI521e7FePVgM9cGPYwhCi6UO8qFOK+YY+Br66ujsmmxb84Yzwp6zCsxjJfWVkxnMsEMGuWHZ9Wcz11cM48hkq0vLwc1tbW4mAwqDpcdIqnMmgF0JMv2CiGnZ2dcHR0FA4PD8Pe3t5U/tx6bCSlb+JT8XfxT3HsUek0Li0tRdjWl+z6iRF+FNA1hXPDQ/IMNyRg3s8bD/OaZS+VP+9cOLSa64cA34oXZWagDkRzAaJxXaE+ufc4rCN7LrazZ2+8+STtpAL8WYDvpTaHKlkB2iQARMvb2+H27m4YaL7zaDtUw1BZAASi6T8T2UZnPZV2pvnJfCH5p8bewcGB6TrIfz8wBZgHQ83kjpuj6RBYQpuo09Tvmpd7TPe+ktZN8cKwS92KWXGuaqWowlYEwthtMcWOZUNJc8at+zuF/Xkqo69baS7P+AvWjYwJ4jyHXXsEnd74ZO/Pq+uXUuv6WNlso6cvnDsZB1V/unJab3D1/KrJDw9NCM9wHf2FK2ejTKMejnBHfGtfH7LGGCdQDqaqJgfgzWjXK1nYV4jRbPGnxUT7cqUaZfJrVZeOm9QmB21L6xXgbu/ScsYusJFMoU0x2fsamRJOd6kOYDRLUxv94ENZe8+0gM+0dyz+KgU7X8rLHHCIOZyrna4y6ykIu0YCs02TBXmk3PZssmEgaTxTo83xjCIjoE21h0Yah3MrV4+9kR8MaabGze+9NEILGAFE5nMOiiA32KnAr/sb7tED3nzlzC4dB38WMC+EjaqHfqvUKHi2gJPdWQ6AbH8hgyQ7QY6jvjj3QZWvX6pUAtduTX5Dss96Q7NI9RQRJeeKvRFbt0v2gb1Gx/PooJsztn1c1DqpAU3Hde2dB2aEHBhjgOFjMeDvxLafjQ3YZQSgOcHJZX611H45sGLHWvYTz9hiURlpNoBZvxb/Ft9lAQ1DmBfUiR+j1hAPkMBTE9L9+zLva1QvGFHurRBaZ5xLVitoBviiRkD/sIMDztKA5FA0b9/0OclzO2/XAQymJ0TcghZwEo9/AX8gMeAJMOvIsWWt5bwCoiFhVSllrdH0t5Q1JHAFlKJNkvTVdn2GHb9KdmacMT+d/Os05imJUccRX2YuZ93Sxf0Ilc4DPDeAq5SAvFEAY94cQc6BA26dzb4HWAJI4DPmQE5KCVUyvb2FcDZem7JdT2ggKUP3xX6n9XNq1DpzSf4Cy4ZqSlmM8d8AAAAASUVORK5CYII=","LastUsed":"2015-10-31 23:59:56 UTC","Name":"Skydragon","Properties":[{"Name":"Flying","Power":1},{"Name":"Creature","Power":1},{"Name":"Explodey","Power":11}]}],"TeaTime":"15:00:00","Weight":198.6} +{"Age":"17","FavoriteTime":"2000-10-31T23:27:46","IsMagic":true,"Name":"Sabrina","NextVacation":"2017-03-14","Spells":[{"DiscoveredBy":"Salem","Icon":"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAABxpRE9UAAAAAgAAAAAAAAAgAAAAKAAAACAAAAAgAAABxj2CfowAAAGSSURBVHgB7Jc9TsNAEIX3JDkCPUV6KlpKFHEGlD4nyA04ACUXQKTgCEipUnKGNEbP0otentayicZ24SlWs7tjO/N9u/5J2b2+NUtuZcnwYE8BuQPyGZAPwXwLLPk5kG+BJa9+fgfkh1B+CeancL4F8i2Q/wWm/S/w+XFoTseftn0dvhu0OXfhpM+AGvzcEiYVAFisPqE9zrETJhHAlXfg2lglMK9z0f3RBfB+ZyRUV3x+erzsEIjjOBqc1xtNAIrvguybV3A9lkVHxlEE6GrrPb/ZvAySwlUnfCmlPQ+R8JCExvGtcRQBLFwj4FGkznX1VYDKPG/f2/MjwCksXACgdNUxJjwK9xwl4JihOwTFR0kIF+CABEPRnvsvPFctMoYKqAFSAFaMwB4pp3Y+bodIYL9WmIAaIOHxo7W8wiHvAjTvhUeNwwSgeAeAABbqOewC5hBdwFD4+9+7puzXV9fS6/b1wwT4tsaYAhwOOQdUQch5vgZCeAhAv3ZM31yYAAUgvApQQQ6n5w6FB/RVe1jdJOAPAAD//1eMQwoAAAGQSURBVO1UMU4DQQy8X9AgWopIUINEkS4VlJQo4gvwAV7AD3gEH4iSgidESpWSXyyZExP5lr0c7K5PsXBhec/2+jzjuWtent9CLdtu1mG5+gjz+WNr7IsY7eH+tvO+xfuqk4vz7CH91edFaF5v9nb6dBKm13edvrL+0Lk5lMzJkQDeJSkkgHF6mR8CHwMHCQR/NAQQGD0BAlwK4FCefQiefq+A2Vn29tG7igLAfmwcnJu/nJy3BMQkMN9HEPr8AL3bfBv7Bp+7/SoExMDjZwKEJwmyhnnmQIQEBIlz2x0iKoAvJkAC6TsTIH6MqRrEWUMSZF2zAwqT4Eu/e6pzFAIkmNSZ4OFT+VYBIIF//UqbJwnF/4DU0GwOn8r/JQYCpPGufEfJuZiA37ycQw/5uFeqPq4pfR6FADmkBCXjfWdZj3NfXW58dAJyB9W65wRoMWulryvAyqa05nQFaDFrpa8rwMqmtOZ0BWgxa6WvK8DKprTmdAVoMWulryvAyqa05nQFaDFrpa8rwMqmtOb89wr4AtQ4aPoL6yVpAAAAAElFTkSuQmCC","LastUsed":"2017-02-14 12:07:23 UTC","Name":"Talking cats","Properties":[{"Name":"Makes you look crazy","Power":1}]}],"TeaTime":"12:00:00","Weight":128.3} diff --git a/test/dataset.js b/test/dataset.js index b0c132f2..56da63e8 100644 --- a/test/dataset.js +++ b/test/dataset.js @@ -67,6 +67,8 @@ describe('BigQuery/Dataset', function() { createDataset: util.noop, }; var DATASET_ID = 'kittens'; + var LOCATION = 'asia-northeast1'; + var Dataset; var Table; var ds; @@ -99,22 +101,12 @@ describe('BigQuery/Dataset', function() { assert(promisified); }); - it('should inherit from ServiceObject', function(done) { - var bigQueryInstance = extend({}, BIGQUERY, { - createDataset: { - bind: function(context) { - assert.strictEqual(context, bigQueryInstance); - done(); - }, - }, - }); - - var ds = new Dataset(bigQueryInstance, DATASET_ID); + it('should inherit from ServiceObject', function() { assert(ds instanceof ServiceObject); var calledWith = ds.calledWith_[0]; - assert.strictEqual(calledWith.parent, bigQueryInstance); + assert.strictEqual(calledWith.parent, BIGQUERY); assert.strictEqual(calledWith.baseUrl, '/datasets'); assert.strictEqual(calledWith.id, DATASET_ID); assert.deepEqual(calledWith.methods, { @@ -126,6 +118,55 @@ describe('BigQuery/Dataset', function() { }); }); + it('should capture user provided location', function() { + var options = {location: LOCATION}; + var ds = new Dataset(BIGQUERY, DATASET_ID, options); + + assert.strictEqual(ds.location, LOCATION); + }); + + describe('createMethod', function() { + var bq; + var ds; + var config; + + beforeEach(function() { + bq = extend(true, {}, BIGQUERY); + ds = new Dataset(bq, DATASET_ID); + config = ds.calledWith_[0]; + }); + + it('should call through to BigQuery#createDataset', function(done) { + var OPTIONS = {}; + + bq.createDataset = function(id, options, callback) { + assert.strictEqual(id, DATASET_ID); + assert.deepEqual(options, OPTIONS); + callback(); // the done fn + }; + + config.createMethod(DATASET_ID, OPTIONS, done); + }); + + it('should optionally accept options', function(done) { + bq.createDataset = function(id, options, callback) { + callback(); // the done fn + }; + + config.createMethod(DATASET_ID, done); + }); + + it('should pass the location', function(done) { + bq.createDataset = function(id, options, callback) { + assert.strictEqual(options.location, LOCATION); + callback(); // the done fn + }; + + ds.location = LOCATION; + config.createMethod(DATASET_ID, done); + }); + }); + describe('etag interceptor', function() { var FAKE_ETAG = 'abc'; @@ -189,11 +230,18 @@ describe('BigQuery/Dataset', function() { a: {b: 'c'}, }; - var expectedOptions = extend(true, {}, fakeOptions, { - defaultDataset: { - datasetId: ds.id, + var expectedOptions = extend( + true, + { + location: LOCATION, }, - }); + fakeOptions, + { + defaultDataset: { + datasetId: ds.id, + }, + } + ); ds.bigQuery.createQueryJob = function(options, callback) { assert.deepEqual(options, expectedOptions); @@ -201,6 +249,7 @@ describe('BigQuery/Dataset', function() { callback(); // the done fn }; + ds.location = LOCATION; ds.createQueryJob(fakeOptions, done); }); @@ -268,6 +317,16 @@ describe('BigQuery/Dataset', function() { ds.createQueryStream(options); }); + it('should extend options with the location', function(done) { + ds.bigQuery.createQueryStream = function(opts) { + assert.strictEqual(opts.location, LOCATION); + done(); + }; + + ds.location = LOCATION; + ds.createQueryStream(); + }); + it('should not modify original options object', function(done) { ds.bigQuery.createQueryStream = function() { assert.deepEqual(options, {a: 'b', c: 'd'}); @@ -417,6 +476,22 @@ describe('BigQuery/Dataset', function() { }); }); + it('should pass the location to the Table', function(done) { + var response = extend({location: LOCATION}, API_RESPONSE); + + ds.request = function(reqOpts, callback) { + callback(null, response); + }; + + ds.table = function(id, options) { + assert.strictEqual(options.location, LOCATION); + setImmediate(done); + return {}; + }; + + ds.createTable(TABLE_ID, {schema: SCHEMA_OBJECT}, assert.ifError); + }); + it('should return an apiResponse', function(done) { var opts = {id: TABLE_ID, schema: SCHEMA_OBJECT}; @@ -556,14 +631,14 @@ describe('BigQuery/Dataset', function() { }); describe('success', function() { + var tableId = 'tableName'; var apiResponse = { tables: [ { a: 'b', c: 'd', - tableReference: { - tableId: 'tableName', - }, + tableReference: {tableId}, + location: LOCATION, }, ], }; @@ -577,8 +652,12 @@ describe('BigQuery/Dataset', function() { it('should return Table & apiResponse', function(done) { ds.getTables(function(err, tables, nextQuery, apiResponse_) { assert.ifError(err); - assert(tables[0] instanceof Table); - assert(tables[0].id, apiResponse.tables[0].tableReference.tableId); + + var table = tables[0]; + + assert(table instanceof Table); + assert.strictEqual(table.id, tableId); + assert.strictEqual(table.location, LOCATION); assert.strictEqual(apiResponse_, apiResponse); done(); }); @@ -661,6 +740,16 @@ describe('BigQuery/Dataset', function() { ds.query(options); }); + it('should extend options with the location', function(done) { + ds.bigQuery.query = function(opts) { + assert.strictEqual(opts.location, LOCATION); + done(); + }; + + ds.location = LOCATION; + ds.query(); + }); + it('should not modify original options object', function(done) { ds.bigQuery.query = function() { assert.deepEqual(options, {a: 'b', c: 'd'}); @@ -689,5 +778,21 @@ describe('BigQuery/Dataset', function() { assert(table instanceof Table); assert.equal(table.id, tableId); }); + + it('should inherit the dataset location', function() { + ds.location = LOCATION; + var table = ds.table('tableId'); + + assert.strictEqual(table.location, LOCATION); + }); + + it('should pass along the location if provided', function() { + ds.location = LOCATION; + + var location = 'US'; + var table = ds.table('tableId', {location}); + + assert.strictEqual(table.location, location); + }); }); }); diff --git a/test/index.js b/test/index.js index 4b330f5f..719999a2 100644 --- a/test/index.js +++ b/test/index.js @@ -30,6 +30,10 @@ var util = require('@google-cloud/common').util; var fakeUuid = extend(true, {}, fakeUuid); +function FakeApiError() { + this.calledWith_ = arguments; +} + var promisified = false; var fakeUtil = extend({}, util, { promisifyAll: function(Class, options) { @@ -47,13 +51,22 @@ var fakeUtil = extend({}, util, { 'timestamp', ]); }, + ApiError: FakeApiError, }); var originalFakeUtil = extend(true, {}, fakeUtil); +function FakeDataset() { + this.calledWith_ = arguments; +} + function FakeTable(a, b) { Table.call(this, a, b); } +function FakeJob() { + this.calledWith_ = arguments; +} + var mergeSchemaWithRowsOverride; FakeTable.mergeSchemaWithRows_ = function() { var args = [].slice.apply(arguments); @@ -90,6 +103,7 @@ nodeutil.inherits(FakeService, Service); describe('BigQuery', function() { var JOB_ID = 'JOB_ID'; var PROJECT_ID = 'test-project'; + var LOCATION = 'asia-northeast1'; var BigQueryCached; var BigQuery; @@ -98,6 +112,8 @@ describe('BigQuery', function() { before(function() { BigQuery = proxyquire('../', { uuid: fakeUuid, + './dataset.js': FakeDataset, + './job.js': FakeJob, './table.js': FakeTable, '@google-cloud/common': { Service: FakeService, @@ -161,6 +177,15 @@ describe('BigQuery', function() { ]); assert.deepEqual(calledWith.packageJson, require('../package.json')); }); + + it('should capture any user specified location', function() { + var bq = new BigQuery({ + projectId: PROJECT_ID, + location: LOCATION, + }); + + assert.strictEqual(bq.location, LOCATION); + }); }); describe('mergeSchemaWithRows_', function() { @@ -724,10 +749,8 @@ describe('BigQuery', function() { bq.request = function(reqOpts) { assert.strictEqual(reqOpts.method, 'POST'); assert.strictEqual(reqOpts.uri, '/datasets'); - assert.deepEqual(reqOpts.json, { - datasetReference: { - datasetId: DATASET_ID, - }, + assert.deepEqual(reqOpts.json.datasetReference, { + datasetId: DATASET_ID, }); done(); @@ -736,6 +759,20 @@ describe('BigQuery', function() { bq.createDataset(DATASET_ID, assert.ifError); }); + it('should send the location if available', function(done) { + var bq = new BigQuery({ + projectId: PROJECT_ID, + location: LOCATION, + }); + + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.location, LOCATION); + done(); + }; + + bq.createDataset(DATASET_ID, assert.ifError); + }); + it('should not modify the original options object', function(done) { var options = { a: 'b', @@ -773,7 +810,7 @@ describe('BigQuery', function() { bq.createDataset(DATASET_ID, function(err, dataset) { assert.ifError(err); - assert.equal(dataset.constructor.name, 'Dataset'); + assert(dataset instanceof FakeDataset); done(); }); }); @@ -808,6 +845,15 @@ describe('BigQuery', function() { }); describe('createJob', function() { + var RESPONSE = { + status: { + state: 'RUNNING', + }, + jobReference: { + location: LOCATION, + }, + }; + var fakeJobId; beforeEach(function() { @@ -827,6 +873,7 @@ describe('BigQuery', function() { jobReference: { projectId: bq.projectId, jobId: fakeJobId, + location: undefined, }, }); @@ -857,6 +904,20 @@ describe('BigQuery', function() { bq.createJob(options, assert.ifError); }); + it('should accept a location', function(done) { + var options = { + location: LOCATION, + }; + + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.jobReference.location, LOCATION); + assert.strictEqual(reqOpts.json.location, undefined); + done(); + }; + + bq.createJob(options, assert.ifError); + }); + it('should accept a job id', function(done) { var jobId = 'job-id'; var options = {jobId}; @@ -870,6 +931,20 @@ describe('BigQuery', function() { bq.createJob(options, assert.ifError); }); + it('should use the user defined location if available', function(done) { + var bq = new BigQuery({ + projectId: PROJECT_ID, + location: LOCATION, + }); + + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.jobReference.location, LOCATION); + done(); + }; + + bq.createJob({}, assert.ifError); + }); + it('should return any request errors', function(done) { var response = {}; var error = new Error('err.'); @@ -886,24 +961,44 @@ describe('BigQuery', function() { }); }); + it('should return any status errors', function(done) { + var errors = [{reason: 'notFound'}]; + var response = extend(true, {}, RESPONSE, { + status: {errors}, + }); + + bq.request = function(reqOpts, callback) { + callback(null, response); + }; + + bq.createJob({}, function(err) { + assert(err instanceof FakeApiError); + + var errorOpts = err.calledWith_[0]; + assert.deepEqual(errorOpts.errors, errors); + assert.strictEqual(errorOpts.response, response); + done(); + }); + }); + it('should return a job object', function(done) { - var response = {}; var fakeJob = {}; - bq.job = function(jobId) { + bq.job = function(jobId, options) { assert.strictEqual(jobId, fakeJobId); + assert.strictEqual(options.location, LOCATION); return fakeJob; }; bq.request = function(reqOpts, callback) { - callback(null, response); + callback(null, RESPONSE); }; bq.createJob({}, function(err, job, resp) { assert.ifError(err); assert.strictEqual(job, fakeJob); - assert.strictEqual(job.metadata, response); - assert.strictEqual(resp, response); + assert.strictEqual(job.metadata, RESPONSE); + assert.strictEqual(resp, RESPONSE); done(); }); }); @@ -1110,6 +1205,21 @@ describe('BigQuery', function() { bq.createQueryJob(options, assert.ifError); }); + it('should accept a location', function(done) { + var options = { + query: QUERY_STRING, + location: LOCATION, + }; + + bq.createJob = function(reqOpts) { + assert.strictEqual(reqOpts.configuration.query.location, undefined); + assert.strictEqual(reqOpts.location, LOCATION); + done(); + }; + + bq.createQueryJob(options, assert.ifError); + }); + it('should accept a job id', function(done) { var options = { query: QUERY_STRING, @@ -1139,13 +1249,39 @@ describe('BigQuery', function() { it('returns a Dataset instance', function() { var ds = bq.dataset(DATASET_ID); - assert.equal(ds.constructor.name, 'Dataset'); + assert(ds instanceof FakeDataset); }); it('should scope the correct dataset', function() { var ds = bq.dataset(DATASET_ID); - assert.equal(ds.id, DATASET_ID); - assert.deepEqual(ds.bigQuery, bq); + var args = ds.calledWith_; + + assert.strictEqual(args[0], bq); + assert.strictEqual(args[1], DATASET_ID); + }); + + it('should accept dataset metadata', function() { + var options = {location: 'US'}; + var ds = bq.dataset(DATASET_ID, options); + var args = ds.calledWith_; + + assert.strictEqual(args[2], options); + }); + + it('should pass the location if available', function() { + var bq = new BigQuery({ + projectId: PROJECT_ID, + location: LOCATION, + }); + + var options = {a: 'b'}; + var expectedOptions = extend({location: LOCATION}, options); + + var ds = bq.dataset(DATASET_ID, options); + var args = ds.calledWith_; + + assert.deepEqual(args[2], expectedOptions); + assert.notStrictEqual(args[2], options); }); }); @@ -1195,15 +1331,29 @@ describe('BigQuery', function() { }); it('should return Dataset objects', function(done) { + var datasetId = 'datasetName'; + bq.request = function(reqOpts, callback) { callback(null, { - datasets: [{datasetReference: {datasetId: 'datasetName'}}], + datasets: [ + { + datasetReference: {datasetId}, + location: LOCATION, + }, + ], }); }; bq.getDatasets(function(err, datasets) { assert.ifError(err); - assert.strictEqual(datasets[0].constructor.name, 'Dataset'); + + var dataset = datasets[0]; + var args = dataset.calledWith_; + + assert(dataset instanceof FakeDataset); + assert.strictEqual(args[0], bq); + assert.strictEqual(args[1], datasetId); + assert.deepEqual(args[2], {location: LOCATION}); done(); }); }); @@ -1319,6 +1469,7 @@ describe('BigQuery', function() { id: JOB_ID, jobReference: { jobId: JOB_ID, + location: LOCATION, }, }, ], @@ -1327,7 +1478,14 @@ describe('BigQuery', function() { bq.getJobs(function(err, jobs) { assert.ifError(err); - assert.strictEqual(jobs[0].constructor.name, 'Job'); + + var job = jobs[0]; + var args = job.calledWith_; + + assert(job instanceof FakeJob); + assert.strictEqual(args[0], bq); + assert.strictEqual(args[1], JOB_ID); + assert.deepEqual(args[2], {location: LOCATION}); done(); }); }); @@ -1398,13 +1556,38 @@ describe('BigQuery', function() { describe('job', function() { it('should return a Job instance', function() { var job = bq.job(JOB_ID); - assert.equal(job.constructor.name, 'Job'); + assert(job instanceof FakeJob); }); it('should scope the correct job', function() { var job = bq.job(JOB_ID); - assert.equal(job.id, JOB_ID); - assert.deepEqual(job.bigQuery, bq); + var args = job.calledWith_; + + assert.strictEqual(args[0], bq); + assert.strictEqual(args[1], JOB_ID); + }); + + it('should pass the options object', function() { + var options = {a: 'b'}; + var job = bq.job(JOB_ID, options); + + assert.strictEqual(job.calledWith_[2], options); + }); + + it('should pass in the user specified location', function() { + var bq = new BigQuery({ + projectId: PROJECT_ID, + location: LOCATION, + }); + + var options = {a: 'b'}; + var expectedOptions = extend({location: LOCATION}, options); + + var job = bq.job(JOB_ID, options); + var args = job.calledWith_; + + assert.deepEqual(args[2], expectedOptions); + assert.notStrictEqual(args[2], options); }); }); diff --git a/test/job.js b/test/job.js index b676b57f..c9231ebd 100644 --- a/test/job.js +++ b/test/job.js @@ -18,7 +18,7 @@ var arrify = require('arrify'); var assert = require('assert'); -var is = require('is'); +var extend = require('extend'); var proxyquire = require('proxyquire'); var util = require('@google-cloud/common').util; @@ -68,6 +68,8 @@ describe('BigQuery/Job', function() { Promise: Promise, }; var JOB_ID = 'job_XYrk_3z'; + var LOCATION = 'asia-northeast1'; + var Job; var job; @@ -110,39 +112,30 @@ describe('BigQuery/Job', function() { assert.deepEqual(calledWith.methods, { exists: true, get: true, - getMetadata: true, setMetadata: true, + getMetadata: { + reqOpts: { + qs: {location: undefined}, + }, + }, }); }); - describe('request interceptor', function() { - it('should assign a request interceptor for /cancel', function() { - var requestInterceptor = job.interceptors.pop().request; - assert(is.fn(requestInterceptor)); - }); + it('should accept a location option', function() { + var options = {location: 'US'}; + var job = new Job(BIGQUERY, JOB_ID, options); - it('should transform `projects` -> `project` for /cancel', function() { - var reqOpts = { - uri: '/bigquery/v2/projects/projectId/jobs/jobId/cancel', - }; - var expectedReqOpts = { - uri: '/bigquery/v2/project/projectId/jobs/jobId/cancel', - }; - - var requestInterceptor = job.interceptors.pop().request; - assert.deepEqual(requestInterceptor(reqOpts), expectedReqOpts); - }); + assert.strictEqual(job.location, options.location); + }); - it('should not affect non-cancel requests', function() { - var reqOpts = { - uri: '/bigquery/v2/projects/projectId/jobs/jobId/getQueryResults', - }; - var expectedReqOpts = { - uri: '/bigquery/v2/projects/projectId/jobs/jobId/getQueryResults', - }; + it('should send the location via getMetadata', function() { + var job = new Job(BIGQUERY, JOB_ID, {location: LOCATION}); + var calledWith = job.calledWith_[0]; - var requestInterceptor = job.interceptors.pop().request; - assert.deepEqual(requestInterceptor(reqOpts), expectedReqOpts); + assert.deepEqual(calledWith.methods.getMetadata, { + reqOpts: { + qs: {location: LOCATION}, + }, }); }); }); @@ -158,32 +151,15 @@ describe('BigQuery/Job', function() { job.cancel(assert.ifError); }); - it('should not require a callback', function(done) { - job.request = function(reqOpts, callback) { - assert.doesNotThrow(function() { - callback(); - done(); - }); - }; - - job.cancel(); - }); - - it('should execute callback with only error & API resp', function(done) { - var arg1 = {}; - var arg2 = {}; - var arg3 = {}; + it('should include the job location', function(done) { + var job = new Job(BIGQUERY, JOB_ID, {location: LOCATION}); - job.request = function(reqOpts, callback) { - callback(arg1, arg2, arg3); + job.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {location: LOCATION}); + done(); }; - job.cancel(function(arg1_, arg2_) { - assert.strictEqual(arguments.length, 2); - assert.strictEqual(arg1_, arg1); - assert.strictEqual(arg2_, arg2); - done(); - }); + job.cancel(assert.ifError); }); }); @@ -192,6 +168,7 @@ describe('BigQuery/Job', function() { var options = { a: 'a', b: 'b', + location: 'US', }; var RESPONSE = { @@ -210,20 +187,31 @@ describe('BigQuery/Job', function() { }); it('should make the correct request', function(done) { - var options = {}; - BIGQUERY.request = function(reqOpts) { assert.strictEqual(reqOpts.uri, '/queries/' + JOB_ID); - assert.strictEqual(reqOpts.qs, options); done(); }; - job.getQueryResults(options, assert.ifError); + job.getQueryResults(assert.ifError); }); it('should optionally accept options', function(done) { + var options = {a: 'b'}; + var expectedOptions = extend({location: undefined}, options); + + BIGQUERY.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, expectedOptions); + done(); + }; + + job.getQueryResults(options, assert.ifError); + }); + + it('should inherit the location', function(done) { + var job = new Job(BIGQUERY, JOB_ID, {location: LOCATION}); + BIGQUERY.request = function(reqOpts) { - assert.deepEqual(reqOpts.qs, {}); + assert.deepEqual(reqOpts.qs, {location: LOCATION}); done(); }; diff --git a/test/table.js b/test/table.js index c802fc0c..a04346a1 100644 --- a/test/table.js +++ b/test/table.js @@ -103,6 +103,8 @@ describe('BigQuery/Table', function() { 'hair_count:float', ].join(','); + var LOCATION = 'asia-northeast1'; + var Table; var TABLE_ID = 'kittens'; var table; @@ -190,6 +192,13 @@ describe('BigQuery/Table', function() { }); }); + it('should capture the location', function() { + var options = {location: LOCATION}; + var table = new Table(DATASET, TABLE_ID, options); + + assert.strictEqual(table.location, LOCATION); + }); + describe('etag interceptor', function() { var FAKE_ETAG = 'abc'; @@ -600,6 +609,16 @@ describe('BigQuery/Table', function() { table.createCopyJob(DEST_TABLE, options, done); }); + it('should use the default location', function(done) { + table.bigQuery.createJob = function(reqOpts, callback) { + assert.strictEqual(reqOpts.location, LOCATION); + callback(); // the done fn + }; + + table.location = LOCATION; + table.createCopyJob(DEST_TABLE, done); + }); + it('should accept a job id', function(done) { var jobId = 'job-id'; var options = {jobId}; @@ -722,6 +741,16 @@ describe('BigQuery/Table', function() { table.createCopyFromJob(SOURCE_TABLE, options, done); }); + it('should use the default location', function(done) { + table.bigQuery.createJob = function(reqOpts, callback) { + assert.strictEqual(reqOpts.location, LOCATION); + callback(); // the done fn + }; + + table.location = LOCATION; + table.createCopyFromJob(SOURCE_TABLE, done); + }); + it('should accept a job id', function(done) { var jobId = 'job-id'; var options = {jobId}; @@ -916,6 +945,17 @@ describe('BigQuery/Table', function() { table.createExtractJob(FILE, options, done); }); + it('should use the default location', function(done) { + var table = new Table(DATASET, TABLE_ID, {location: LOCATION}); + + table.bigQuery.createJob = function(reqOpts, callback) { + assert.strictEqual(reqOpts.location, LOCATION); + callback(); // the done fn + }; + + table.createExtractJob(FILE, done); + }); + it('should accept a job id', function(done) { var jobId = 'job-id'; var options = {jobId}; @@ -1134,6 +1174,17 @@ describe('BigQuery/Table', function() { ); }); + it('should use the default location', function(done) { + var table = new Table(DATASET, TABLE_ID, {location: LOCATION}); + + table.bigQuery.createJob = function(reqOpts, callback) { + assert.strictEqual(reqOpts.location, LOCATION); + callback(); // the done fn + }; + + table.createLoadJob(FILE, done); + }); + it('should accept a job id', function(done) { var jobId = 'job-id'; var options = {jobId}; @@ -1339,6 +1390,7 @@ describe('BigQuery/Table', function() { jobReference: { projectId: table.bigQuery.projectId, jobId: fakeJobId, + location: undefined, }, }); done(); @@ -1377,6 +1429,19 @@ describe('BigQuery/Table', function() { table.createWriteStream({jobPrefix: jobPrefix}).emit('writing'); }); + it('should use the default location', function(done) { + var table = new Table(DATASET, TABLE_ID, {location: LOCATION}); + + makeWritableStreamOverride = function(stream, options) { + var location = options.metadata.jobReference.location; + assert.strictEqual(location, LOCATION); + + done(); + }; + + table.createWriteStream().emit('writing'); + }); + it('should accept a job id', function(done) { var jobId = 'job-id'; var options = {jobId}; @@ -1396,9 +1461,14 @@ describe('BigQuery/Table', function() { it('should create a job and emit it with complete', function(done) { var jobId = 'job-id'; - var metadata = {jobReference: {jobId: jobId}, a: 'b', c: 'd'}; + var metadata = { + jobReference: {jobId, location: LOCATION}, + a: 'b', + c: 'd', + }; - table.bigQuery.job = function(id) { + table.bigQuery.job = function(id, options) { + assert.strictEqual(options.location, LOCATION); return {id: id}; };