GitHub PullRequestReviewCommentEvent at 2014-10-30 21:07:31
Repository: gcloud-node
Actor: ryanseys
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/comments/19636433","id":19636433,"diff_hunk":"@@ -0,0 +1,915 @@\n+/**\n+ * Copyright 2014 Google Inc. All Rights Reserved.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ *      http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+/*global describe, it, beforeEach */\n+\n+'use strict';\n+\n+var assert = require('assert');\n+var File = require('../../lib/storage/file');\n+var Stream = require('stream');\n+var through = require('through2');\n+var util = require('../../lib/common/util');\n+\n+function FakeFile(a, b) {\n+  File.call(this, a, b);\n+}\n+\n+var makeWritableStream_Override;\n+var fakeUtil = require('extend')(util, {\n+  makeWritableStream: function() {\n+    var args = [].slice.call(arguments);\n+    (makeWritableStream_Override || util.makeWritableStream).apply(null, args);\n+    makeWritableStream_Override = null;\n+  }\n+});\n+\n+var Table = require('sandboxed-module')\n+  .require('../../lib/bigquery/table', {\n+    requires: {\n+      '../storage/file': FakeFile,\n+      '../common/util': fakeUtil\n+    }\n+  });\n+\n+describe('BigQuery/Table', function() {\n+  var DATASET = {\n+    id: 'dataset-id',\n+    bigQuery: {\n+      makeReq_: util.noop,\n+      job: function(id) {\n+        return { id: id };\n+      },\n+      projectId: 'project-id',\n+    }\n+  };\n+\n+  var SCHEMA_OBJECT = {\n+    fields: [\n+      { name: 'id', type: 'integer' },\n+      { name: 'breed', type: 'string' },\n+      { name: 'name', type: 'string' },\n+      { name: 'dob', type: 'timestamp' }\n+    ]\n+  };\n+  var SCHEMA_STRING = 'id:integer,breed,name,dob:timestamp';\n+\n+  var TABLE_ID = 'kittens';\n+  var table;\n+\n+  beforeEach(function() {\n+    table = new Table(DATASET, TABLE_ID);\n+  });\n+\n+  describe('createSchemaFromString_', function() {\n+    it('should create a schema object from a string', function() {\n+      assert.deepEqual(\n+        Table.createSchemaFromString_(SCHEMA_STRING), SCHEMA_OBJECT);\n+    });\n+  });\n+\n+  describe('mergeSchemaWithRows_', function() {\n+    it('should merge the schema and flatten the rows', function() {\n+      var rows = [\n+        {\n+          f: [\n+            { v: 3 },\n+            { v: 'dogbreed' },\n+            { v: 'dogname' },\n+            { v: '234234' }\n+          ]\n+        },\n+        {\n+          f: [\n+            { v: 4 },\n+            { v: 'dogbreed2' },\n+            { v: 'dogname2' },\n+            { v: '2342342' }\n+          ]\n+        }\n+      ];\n+\n+      assert.deepEqual(Table.mergeSchemaWithRows_(SCHEMA_OBJECT, rows), [\n+        { id: 3, breed: 'dogbreed', name: 'dogname', dob: '234234' },\n+        { id: 4, breed: 'dogbreed2', name: 'dogname2', dob: '2342342' }\n+      ]);\n+    });\n+  });\n+\n+  describe('copy', function() {\n+    var DEST_TABLE = new Table(DATASET, 'destination-table');\n+\n+    it('should throw if a destination is not a Table', function() {\n+      assert.throws(function() {\n+        table.copy();\n+      }, /Destination must be a Table/);\n+\n+      assert.throws(function() {\n+        table.copy({});\n+      }, /Destination must be a Table/);\n+\n+      assert.throws(function() {\n+        table.copy(function() {});\n+      }, /Destination must be a Table/);\n+    });\n+\n+    it('should send correct request to the API', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        assert.equal(method, 'POST');\n+        assert.equal(path, '/jobs');\n+        assert.strictEqual(query, null);\n+        assert.deepEqual(body, {\n+          configuration: {\n+            copy: {\n+              a: 'b',\n+              c: 'd',\n+              destinationTable: {\n+                datasetId: DEST_TABLE.dataset.id,\n+                projectId: DEST_TABLE.bigQuery.projectId,\n+                tableId: DEST_TABLE.id\n+              },\n+              sourceTable: {\n+                datasetId: table.dataset.id,\n+                projectId: table.bigQuery.projectId,\n+                tableId: table.id\n+              }\n+            }\n+          }\n+        });\n+        done();\n+      };\n+\n+      table.copy(DEST_TABLE, { a: 'b', c: 'd' }, assert.ifError);\n+    });\n+\n+    it('should create and return a Job', function(done) {\n+      var jobId = 'job-id';\n+\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, { jobReference: { jobId: jobId } });\n+      };\n+\n+      table.copy(DEST_TABLE, function(err, job) {\n+        assert.ifError(err);\n+        assert.equal(job.id, jobId);\n+        done();\n+      });\n+    });\n+\n+    it('should assign metadata on the job', function(done) {\n+      var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' };\n+\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, jobMetadata);\n+      };\n+\n+      table.copy(DEST_TABLE, function(err, job) {\n+        assert.ifError(err);\n+        assert.deepEqual(job.metadata, jobMetadata);\n+        done();\n+      });\n+    });\n+\n+    it('should accept just a destination and callback', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, { jobReference: { jobId: 'job-id' } });\n+      };\n+\n+      table.copy(DEST_TABLE, done);\n+    });\n+\n+    it('should pass an error to the callback', function(done) {\n+      var error = new Error('Error.');\n+\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(error);\n+      };\n+\n+      table.copy(DEST_TABLE, function(err) {\n+        assert.equal(err, error);\n+        done();\n+      });\n+    });\n+  });\n+\n+  describe('createReadStream', function() {\n+    it('returns a stream', function() {\n+      assert(table.createReadStream() instanceof Stream);\n+    });\n+\n+    it('calls getRows() when asked for data', function(done) {\n+      table.getRows = function() {\n+        done();\n+      };\n+      table.createReadStream().emit('reading');\n+    });\n+\n+    describe('errors', function() {\n+      var error = new Error('Error.');\n+\n+      beforeEach(function() {\n+        table.getRows = function(handler) {\n+          handler(error);\n+        };\n+      });\n+\n+      it('emits errors', function(done) {\n+        table.createReadStream()\n+          .once('error', function(err) {\n+            assert.equal(err, error);\n+            done();\n+          })\n+          .emit('reading');\n+      });\n+\n+      it('ends the stream', function(done) {\n+        table.createReadStream()\n+          .once('error', util.noop)\n+          .once('finish', done)\n+          .emit('reading');\n+      });\n+    });\n+\n+    it('emits rows', function(done) {\n+      var rows = [{ a: 'b' }, { c: 'd' }];\n+      var rowsEmitted = 0;\n+\n+      table.getRows = function(handler) {\n+        handler(null, rows);\n+      };\n+\n+      table.createReadStream()\n+        .on('data', function(row) {\n+          rowsEmitted++;\n+\n+          assert.deepEqual(row, rows[rowsEmitted - 1]);\n+\n+          if (rowsEmitted === rows.length) {\n+            done();\n+          }\n+        });\n+    });\n+\n+    it('calls getRows() if nextQuery exists', function(done) {\n+      var called = 0;\n+\n+      var nextQuery = { a: 'b', c: 'd' };\n+      var responseHandler;\n+\n+      table.getRows = function(query, handler) {\n+        responseHandler = responseHandler || handler || query;\n+\n+        if (++called === 2) {\n+          assert.deepEqual(query, nextQuery);\n+          assert.equal(responseHandler, handler);\n+          done();\n+        } else {\n+          responseHandler(null, [], nextQuery);\n+        }\n+      };\n+\n+      table.createReadStream().emit('reading');\n+    });\n+\n+    it('ends stream when nextQuery is not present', function(done) {\n+      table.getRows = function(handler) {\n+        handler(null, []);\n+      };\n+\n+      table.createReadStream().on('finish', done).emit('reading');\n+    });\n+  });\n+\n+  describe('createWriteStream', function() {\n+    it('uses a string as the file type', function(done) {\n+      var fileType = 'csv';\n+\n+      makeWritableStream_Override = function(stream, options) {\n+        var load = options.metadata.configuration.load;\n+        assert.equal(load.sourceFormat, 'CSV');\n+        done();\n+      };\n+\n+      table.createWriteStream(fileType).emit('writing');\n+    });\n+\n+    it('throws if a provided source format is not recognized', function() {\n+      assert.throws(function() {\n+        table.createWriteStream('zip');\n+      }, /Source format not recognized/);\n+\n+      assert.throws(function() {\n+        table.createWriteStream({\n+          sourceFormat: 'zip'\n+        });\n+      }, /Source format not recognized/);\n+\n+      assert.doesNotThrow(function() {\n+        table.createWriteStream();\n+        table.createWriteStream({});\n+      });\n+    });\n+\n+    it('returns a stream', function() {\n+      assert(table.createWriteStream() instanceof Stream);\n+    });\n+\n+    describe('writable stream', function() {\n+      it('makes a writable stream when written to', function(done) {\n+        var stream;\n+\n+        makeWritableStream_Override = function(s) {\n+          assert.equal(s, stream);\n+          done();\n+        };\n+\n+        stream = table.createWriteStream();\n+        stream.emit('writing');\n+      });\n+\n+      it('passes the connection', function(done) {\n+        makeWritableStream_Override = function(stream, options) {\n+          assert.deepEqual(options.connection, table.connection);\n+          done();\n+        };\n+\n+        table.createWriteStream().emit('writing');\n+      });\n+\n+      it('passes extended metadata', function(done) {\n+        makeWritableStream_Override = function(stream, options) {\n+          assert.deepEqual(options.metadata, {\n+            configuration: {\n+              load: {\n+                a: 'b',\n+                c: 'd',\n+                destinationTable: {\n+                  projectId: table.bigQuery.projectId,\n+                  datasetId: table.dataset.id,\n+                  tableId: table.id\n+                }\n+              }\n+            }\n+          });\n+          done();\n+        };\n+\n+        table.createWriteStream({ a: 'b', c: 'd' }).emit('writing');\n+      });\n+\n+      it('passes the correct request uri', function(done) {\n+        makeWritableStream_Override = function(stream, options) {\n+          var uri = 'https://www.googleapis.com/upload/bigquery/v2/projects/' +\n+            table.bigQuery.projectId + '/jobs';\n+          assert.equal(options.request.uri, uri);\n+          done();\n+        };\n+\n+        table.createWriteStream().emit('writing');\n+      });\n+\n+      it('creates a job and emits it with complete', function(done) {\n+        var jobId = 'job-id';\n+        var metadata = { jobReference: { jobId: jobId }, a: 'b', c: 'd' };\n+\n+        table.bigQuery.job = function(id) {\n+          return { id: id };\n+        };\n+\n+        makeWritableStream_Override = function(stream, options, callback) {\n+          callback(metadata);\n+        };\n+\n+        table.createWriteStream()\n+          .on('complete', function(job) {\n+            assert.equal(job.id, jobId);\n+            assert.deepEqual(job.metadata, metadata);\n+            done();\n+          })\n+          .emit('writing');\n+      });\n+    });\n+  });\n+\n+  describe('delete', function() {\n+    it('sends correct API request to delete', function(done) {\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        assert.equal(method, 'DELETE');\n+        assert.equal(path, '');\n+        assert.strictEqual(query, null);\n+        assert.strictEqual(body, null);\n+        callback();\n+      };\n+\n+      table.delete(done);\n+    });\n+  });\n+\n+  describe('export', function() {\n+    var FILE = new FakeFile({\n+      name: 'bucket-name',\n+      makeReq_: util.noop\n+    }, 'file.json');\n+\n+    beforeEach(function() {\n+      table.bigQuery.job = function(id) {\n+        return { id: id };\n+      };\n+    });\n+\n+    it('sends correct API request', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        assert.equal(method, 'POST');\n+        assert.equal(path, '/jobs');\n+        assert.strictEqual(query, null);\n+        assert.deepEqual(body.configuration.extract.sourceTable, {\n+          datasetId: table.dataset.id,\n+          projectId: table.bigQuery.projectId,\n+          tableId: table.id\n+        });\n+        done();\n+      };\n+\n+      table.export(FILE, assert.ifError);\n+    });\n+\n+    it('accepts just a destination and a callback', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, { jobReference: { jobId: 'job-id' }});\n+      };\n+\n+      table.export(FILE, done);\n+    });\n+\n+    it('parses out full gs:// urls from files', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        assert.deepEqual(body.configuration.extract.destinationUris, [\n+          'gs://' + FILE.bucket.name + '/' + FILE.name\n+        ]);\n+        done();\n+      };\n+\n+      table.export(FILE, done);\n+    });\n+\n+    it('throws if a destination is not a File', function() {\n+      assert.throws(function() {\n+        table.export({}, util.noop);\n+      }, /Destination must be a File object/);\n+\n+      assert.throws(function() {\n+        table.export([FILE, {}], util.noop);\n+      }, /Destination must be a File object/);\n+    });\n+\n+    it('detects file format if a format is not provided', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        var destinationFormat = body.configuration.extract.destinationFormat;\n+        assert.equal(destinationFormat, 'NEWLINE_DELIMITED_JSON');\n+        done();\n+      };\n+\n+      table.export(FILE, assert.ifError);\n+    });\n+\n+    it('assigns the provided format if matched', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        assert.equal(body.configuration.extract.destinationFormat, 'CSV');\n+        assert.strictEqual(body.configuration.extract.format, undefined);\n+        done();\n+      };\n+\n+      table.export(FILE, { format: 'csv' }, assert.ifError);\n+    });\n+\n+    it('throws if a provided format is not recognized', function() {\n+      assert.throws(function() {\n+        table.export(FILE, { format: 'zip' }, util.noop);\n+      }, /Destination format not recognized/);\n+    });\n+\n+    it('assigns GZIP compression with gzip: true', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        assert.equal(body.configuration.extract.compression, 'GZIP');\n+        assert.strictEqual(body.configuration.extract.gzip, undefined);\n+        done();\n+      };\n+\n+      table.export(FILE, { gzip: true }, util.noop);\n+    });\n+\n+    it('executes callback with error', function(done) {\n+      var error = new Error('Error.');\n+\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(error);\n+      };\n+\n+      table.export(FILE, function(err) {\n+        assert.equal(err, error);\n+        done();\n+      });\n+    });\n+\n+    it('creates a Job and returns it to the callback', function(done) {\n+      var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' };\n+\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, jobMetadata);\n+      };\n+\n+      table.export(FILE, function(err, job) {\n+        assert.ifError(err);\n+        assert.deepEqual(job.metadata, jobMetadata);\n+        done();\n+      });\n+    });\n+  });\n+\n+  describe('getMetadata', function() {\n+    it('should get metadata from api', function(done) {\n+      table.makeReq_ = function(method, path, query, body) {\n+        assert.equal(method, 'GET');\n+        assert.equal(path, '');\n+        assert.strictEqual(query, null);\n+        assert.strictEqual(body, null);\n+        done();\n+      };\n+      table.getMetadata(assert.ifError);\n+    });\n+\n+    it('should execute callback with error', function(done) {\n+      var error = new Error('Error.');\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        callback(error);\n+      };\n+      table.getMetadata(function(err) {\n+        assert.equal(err, error);\n+        done();\n+      });\n+    });\n+\n+    describe('metadata', function() {\n+      var METADATA = { a: 'b', c: 'd' };\n+\n+      beforeEach(function() {\n+        table.makeReq_ = function(method, path, query, body, callback) {\n+          callback(null, METADATA);\n+        };\n+      });\n+\n+      it('should update metadata on Dataset object', function(done) {\n+        table.getMetadata(function(err) {\n+          assert.ifError(err);\n+          assert.deepEqual(table.metadata, METADATA);\n+          done();\n+        });\n+      });\n+\n+      it('should execute callback with metadata', function(done) {\n+        table.getMetadata(function(err, metadata) {\n+          assert.ifError(err);\n+          assert.deepEqual(metadata, METADATA);\n+          done();\n+        });\n+      });\n+    });\n+  });\n+\n+  describe('getRows', function() {\n+    it('should accept just a callback', function(done) {\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, {});\n+      };\n+      table.getRows(done);\n+    });\n+\n+    it('should make correct API request', function(done) {\n+      var options = { a: 'b', c: 'd' };\n+\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        assert.equal(method, 'GET');\n+        assert.equal(path, '/data');\n+        assert.deepEqual(query, options);\n+        assert.strictEqual(body, null);\n+        callback(null, {});\n+      };\n+\n+      table.getRows(options, done);\n+    });\n+\n+    it('should refresh metadata if it does not have a schema', function(done) {\n+      // Step 0: use \"Stephen\" so you know who to blame for this test.\n+      var rows = [{ f: [{ v: 'stephen' }] }];\n+      var schema = { fields: [{ name: 'name', type: 'string' }] };\n+\n+      // Step 1: makes the request.\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        // Respond with a row, so it grabs the schema.\n+        // Use setImmediate to let our getMetadata overwrite process.\n+        setImmediate(callback, null, { rows: rows });\n+      };\n+      table.getRows(responseHandler);\n+\n+      // Step 2: refreshes the metadata to pull down the schema.\n+      table.getMetadata = function(callback) {\n+        table.metadata = { schema: schema };\n+        callback();\n+      };\n+\n+      // Step 3: execute original complete handler with schema-merged rows.\n+      function responseHandler(err, rows) {\n+        assert.ifError(err);\n+        assert.deepEqual(rows, [{ name: 'stephen' }]);\n+        done();\n+      }\n+    });\n+\n+    it('should return schema-merged rows', function(done) {\n+      var rows = [{ f: [{ v: 'stephen' }] }];\n+      var schema = { fields: [{ name: 'name', type: 'string' }] };\n+      table.metadata = { schema: schema };\n+\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, { rows: rows });\n+      };\n+\n+      table.getRows(function(err, rows) {\n+        assert.ifError(err);\n+        assert.deepEqual(rows, [{ name: 'stephen' }]);\n+        done();\n+      });\n+    });\n+\n+    it('should pass nextQuery if pageToken is returned', function(done) {\n+      var options = { a: 'b', c: 'd' };\n+      var pageToken = 'token';\n+\n+      // Set a schema so it doesn't try to refresh the metadata.\n+      table.metadata = { schema: {} };\n+\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, { pageToken: pageToken });\n+      };\n+\n+      table.getRows(options, function(err, rows, nextQuery) {\n+        assert.ifError(err);\n+        assert.deepEqual(nextQuery, { a: 'b', c: 'd', pageToken: pageToken });\n+        // Original object isn't affected.\n+        assert.deepEqual(options, { a: 'b', c: 'd' });\n+        done();\n+      });\n+    });\n+  });\n+\n+  describe('import', function() {\n+    var FILEPATH = require.resolve('../testdata/response_get.json');\n+    var FILE = new FakeFile({\n+      name: 'bucket-name',\n+      makeReq_: util.noop\n+    }, 'file.json');\n+\n+    it('should accept just a File and a callback', function(done) {\n+      table.createWriteStream = function() {\n+        var stream = through();\n+        setImmediate(function() {\n+          stream.emit('complete');\n+        });\n+        return stream;\n+      };\n+\n+      table.import(FILEPATH, done);\n+    });\n+\n+    it('should return a stream when a string is given', function() {\n+      table.createWriteStream = through;\n+\n+      assert(table.import(FILEPATH) instanceof Stream);\n+    });\n+\n+    it('should infer the file format from the given filepath', function(done) {\n+      table.createWriteStream = function(metadata) {\n+        assert.equal(metadata.sourceFormat, 'NEWLINE_DELIMITED_JSON');\n+        done();\n+        return through(); // so it can register the event listeners.\n+      };\n+\n+      table.import(FILEPATH, assert.ifError);\n+    });\n+\n+    it('should not infer the file format if one is given', function(done) {\n+      table.createWriteStream = function(metadata) {\n+        assert.equal(metadata.sourceFormat, 'CSV');\n+        done();\n+        return through();\n+      };\n+\n+      table.import(FILEPATH, { sourceFormat: 'CSV' }, assert.ifError);\n+    });\n+\n+    it('should throw if a File object is not provided', function() {\n+      assert.throws(function() {\n+        table.import({});\n+      }, /Source must be a File object/);\n+    });\n+\n+    it('convert File objects to gs:// urls', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        var sourceUri = body.configuration.load.sourceUris[0];\n+        assert.equal(sourceUri, 'gs://' + FILE.bucket.name + '/' + FILE.name);\n+        done();\n+      };\n+\n+      table.import(FILE, assert.ifError);\n+    });\n+\n+    it('should infer the file format from a File object', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        var sourceFormat = body.configuration.load.sourceFormat;\n+        assert.equal(sourceFormat, 'NEWLINE_DELIMITED_JSON');\n+        done();\n+      };\n+\n+      table.import(FILE, assert.ifError);\n+    });\n+\n+    it('should not override a provided format with a File', function(done) {\n+      table.bigQuery.makeReq_ = function(method, path, query, body) {\n+        var sourceFormat = body.configuration.load.sourceFormat;\n+        assert.equal(sourceFormat, 'CSV');\n+        done();\n+      };\n+\n+      table.import(FILE, { sourceFormat: 'CSV' }, assert.ifError);\n+    });\n+\n+    it('executes callback with error', function(done) {\n+      var error = new Error('Error.');\n+\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(error);\n+      };\n+\n+      table.import(FILE, function(err) {\n+        assert.equal(err, error);\n+        done();\n+      });\n+    });\n+\n+    it('creates a Job and returns it to the callback', function(done) {\n+      var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' };\n+\n+      table.bigQuery.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, jobMetadata);\n+      };\n+\n+      table.import(FILE, function(err, job) {\n+        assert.ifError(err);\n+        assert.deepEqual(job.metadata, jobMetadata);\n+        done();\n+      });\n+    });\n+  });\n+\n+  describe('insert', function() {\n+    var data = [\n+      { state: 'MI', gender: 'M', year: '2015', name: 'Berkley', count: '0' },\n+      { state: 'MI', gender: 'M', year: '2015', name: 'Berkley', count: '0' },\n+      { state: 'MI', gender: 'M', year: '2015', name: 'Berkley', count: '0' },\n+      { state: 'MI', gender: 'M', year: '2015', name: 'Berkley', count: '0' },\n+      { state: 'MI', gender: 'M', year: '2015', name: 'Berkley', count: '0' }\n+    ];\n+\n+    it('should save data', function(done) {\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        callback(null, { insertErrors: [] });\n+      };\n+\n+      table.insert(data, done);\n+    });\n+  });\n+\n+  describe('query', function() {\n+    it('passes arguments through to datasetInstance.query()', function(done) {\n+      table.dataset.query = function(a, b) {\n+        assert.equal(a, 'a');\n+        assert.equal(b, 'b');\n+        done();\n+      };\n+\n+      table.query('a', 'b');\n+    });\n+  });\n+\n+  describe('setMetadata', function() {\n+    var METADATA = { a: 'b', c: 'd' };\n+\n+    it('should send request to the api', function(done) {\n+      table.makeReq_ = function(method, path, query, body) {\n+        assert.equal(method, 'PUT');\n+        assert.equal(path, '');\n+        assert.strictEqual(query, null);\n+        assert.deepEqual(body, METADATA);\n+        done();\n+      };\n+      table.setMetadata(METADATA, assert.ifError);\n+    });\n+\n+    it('should convert a name to a friendly name', function(done) {\n+      var name = 'a new name';\n+      table.makeReq_ = function(method, path, query, body) {\n+        assert.equal(body.friendlyName, name);\n+        done();\n+      };\n+      table.setMetadata({ name: name }, assert.ifError);\n+    });\n+\n+    it('should accept a schema', function(done) {\n+      table.makeReq_ = function(method, path, query, body) {\n+        assert.deepEqual(body.schema, {\n+          fields: [{ name: 'schema', type: 'string' }]\n+        });\n+        done();\n+      };\n+      table.setMetadata({ schema: 'schema' });\n+    });\n+\n+    it('should execute callback with error', function(done) {\n+      var error = new Error('Error.');\n+      table.makeReq_ = function(method, path, query, body, callback) {\n+        callback(error);\n+      };\n+      table.setMetadata(METADATA, function(err) {\n+        assert.equal(err, error);\n+        done();\n+      });\n+    });\n+\n+    describe('metadata', function() {\n+      beforeEach(function() {\n+        table.makeReq_ = function(method, path, query, body, callback) {\n+          callback(null, METADATA);\n+        };\n+      });\n+\n+      it('should update metadata on Dataset object', function(done) {\n+        table.setMetadata(METADATA, function(err) {\n+          assert.ifError(err);\n+          assert.deepEqual(table.metadata, METADATA);\n+          done();\n+        });\n+      });\n+\n+      it('should execute callback with metadata', function(done) {\n+        table.setMetadata(METADATA, function(err, metadata) {\n+          assert.ifError(err);\n+          assert.deepEqual(metadata, METADATA);\n+          done();\n+        });\n+      });\n+    });\n+  });\n+\n+  describe('makeReq_', function() {\n+    it('prefixes the path', function(done) {\n+      var path = '/test-path';\n+\n+      table.dataset.makeReq_ = function(method, p) {\n+        assert.equal(p, '/tables/' + table.id + path);\n+        done();\n+      };\n+\n+      table.makeReq_('POST', path);\n+    });\n+\n+    it('passes through arguments', function(done) {\n+      var method = 'POST';\n+      var query = { a: 'b', c: 'd', e: { f: 'g' } };\n+      var body = { a: 'b', c: 'd', e: { f: 'g' } };\n+      function callback() { console.log('yay!'); }","path":"test/bigquery/table.js","position":902,"original_position":902,"commit_id":"5ad83714ac70188e6c71833e3d6c0e70fc01064b","original_commit_id":"5ad83714ac70188e6c71833e3d6c0e70fc01064b","user":{"login":"ryanseys","id":163873,"avatar_url":"https://avatars.githubusercontent.com/u/163873?v=2","gravatar_id":"","url":"https://api.github.com/users/ryanseys","html_url":"https://github.com/ryanseys","followers_url":"https://api.github.com/users/ryanseys/followers","following_url":"https://api.github.com/users/ryanseys/following{/other_user}","gists_url":"https://api.github.com/users/ryanseys/gists{/gist_id}","starred_url":"https://api.github.com/users/ryanseys/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ryanseys/subscriptions","organizations_url":"https://api.github.com/users/ryanseys/orgs","repos_url":"https://api.github.com/users/ryanseys/repos","events_url":"https://api.github.com/users/ryanseys/events{/privacy}","received_events_url":"https://api.github.com/users/ryanseys/received_events","type":"User","site_admin":false},"body":"Use noop instead.","created_at":"2014-10-30T21:07:31Z","updated_at":"2014-10-30T21:07:31Z","html_url":"https://github.com/GoogleCloudPlatform/gcloud-node/pull/264#discussion_r19636433","pull_request_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264","_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/comments/19636433"},"html":{"href":"https://github.com/GoogleCloudPlatform/gcloud-node/pull/264#discussion_r19636433"},"pull_request":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264"}}},"pull_request":{"url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264","id":23095748,"html_url":"https://github.com/GoogleCloudPlatform/gcloud-node/pull/264","diff_url":"https://github.com/GoogleCloudPlatform/gcloud-node/pull/264.diff","patch_url":"https://github.com/GoogleCloudPlatform/gcloud-node/pull/264.patch","issue_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/issues/264","number":264,"state":"open","locked":false,"title":"BigQuery","user":{"login":"stephenplusplus","id":723048,"avatar_url":"https://avatars.githubusercontent.com/u/723048?v=2","gravatar_id":"","url":"https://api.github.com/users/stephenplusplus","html_url":"https://github.com/stephenplusplus","followers_url":"https://api.github.com/users/stephenplusplus/followers","following_url":"https://api.github.com/users/stephenplusplus/following{/other_user}","gists_url":"https://api.github.com/users/stephenplusplus/gists{/gist_id}","starred_url":"https://api.github.com/users/stephenplusplus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephenplusplus/subscriptions","organizations_url":"https://api.github.com/users/stephenplusplus/orgs","repos_url":"https://api.github.com/users/stephenplusplus/repos","events_url":"https://api.github.com/users/stephenplusplus/events{/privacy}","received_events_url":"https://api.github.com/users/stephenplusplus/received_events","type":"User","site_admin":false},"body":"#### Docs: https://stephenplusplus.github.io/gcloud-node/#/docs/master/bigquery\r\n\r\n##### Todo List (current):\r\n\r\n- [x] Unit tests\r\n- [x] Regression tests\r\n- [ ] Update use cases from the doc examples to less kitten-based use cases :smirk_cat: ","created_at":"2014-10-21T15:47:00Z","updated_at":"2014-10-30T21:07:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1c3d776a1462d453ed9a1e908f08336246f8358c","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264/commits","review_comments_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264/comments","review_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/comments/{number}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/issues/264/comments","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/statuses/5ad83714ac70188e6c71833e3d6c0e70fc01064b","head":{"label":"stephenplusplus:spp--bigquery","ref":"spp--bigquery","sha":"5ad83714ac70188e6c71833e3d6c0e70fc01064b","user":{"login":"stephenplusplus","id":723048,"avatar_url":"https://avatars.githubusercontent.com/u/723048?v=2","gravatar_id":"","url":"https://api.github.com/users/stephenplusplus","html_url":"https://github.com/stephenplusplus","followers_url":"https://api.github.com/users/stephenplusplus/followers","following_url":"https://api.github.com/users/stephenplusplus/following{/other_user}","gists_url":"https://api.github.com/users/stephenplusplus/gists{/gist_id}","starred_url":"https://api.github.com/users/stephenplusplus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephenplusplus/subscriptions","organizations_url":"https://api.github.com/users/stephenplusplus/orgs","repos_url":"https://api.github.com/users/stephenplusplus/repos","events_url":"https://api.github.com/users/stephenplusplus/events{/privacy}","received_events_url":"https://api.github.com/users/stephenplusplus/received_events","type":"User","site_admin":false},"repo":{"id":22482767,"name":"gcloud-node","full_name":"stephenplusplus/gcloud-node","owner":{"login":"stephenplusplus","id":723048,"avatar_url":"https://avatars.githubusercontent.com/u/723048?v=2","gravatar_id":"","url":"https://api.github.com/users/stephenplusplus","html_url":"https://github.com/stephenplusplus","followers_url":"https://api.github.com/users/stephenplusplus/followers","following_url":"https://api.github.com/users/stephenplusplus/following{/other_user}","gists_url":"https://api.github.com/users/stephenplusplus/gists{/gist_id}","starred_url":"https://api.github.com/users/stephenplusplus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephenplusplus/subscriptions","organizations_url":"https://api.github.com/users/stephenplusplus/orgs","repos_url":"https://api.github.com/users/stephenplusplus/repos","events_url":"https://api.github.com/users/stephenplusplus/events{/privacy}","received_events_url":"https://api.github.com/users/stephenplusplus/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/stephenplusplus/gcloud-node","description":"Google Cloud Node Client Library","fork":true,"url":"https://api.github.com/repos/stephenplusplus/gcloud-node","forks_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/forks","keys_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/teams","hooks_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/hooks","issue_events_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/issues/events{/number}","events_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/events","assignees_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/assignees{/user}","branches_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/branches{/branch}","tags_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/tags","blobs_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/git/refs{/sha}","trees_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/statuses/{sha}","languages_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/languages","stargazers_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/stargazers","contributors_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/contributors","subscribers_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/subscribers","subscription_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/subscription","commits_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/commits{/sha}","git_commits_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/git/commits{/sha}","comments_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/comments{/number}","issue_comment_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/issues/comments/{number}","contents_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/contents/{+path}","compare_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/merges","archive_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/downloads","issues_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/issues{/number}","pulls_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/pulls{/number}","milestones_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/milestones{/number}","notifications_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/labels{/name}","releases_url":"https://api.github.com/repos/stephenplusplus/gcloud-node/releases{/id}","created_at":"2014-07-31T19:55:20Z","updated_at":"2014-07-31T19:55:38Z","pushed_at":"2014-10-30T19:03:46Z","git_url":"git://github.com/stephenplusplus/gcloud-node.git","ssh_url":"git@github.com:stephenplusplus/gcloud-node.git","clone_url":"https://github.com/stephenplusplus/gcloud-node.git","svn_url":"https://github.com/stephenplusplus/gcloud-node","homepage":"","size":8230,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"GoogleCloudPlatform:master","ref":"master","sha":"51abc0dbd59139aa7d44020f5fb5291e56ae660e","user":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars.githubusercontent.com/u/2810941?v=2","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"repo":{"id":19483273,"name":"gcloud-node","full_name":"GoogleCloudPlatform/gcloud-node","owner":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars.githubusercontent.com/u/2810941?v=2","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/GoogleCloudPlatform/gcloud-node","description":"Google Cloud Client Library for Node.js","fork":false,"url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node","forks_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/forks","keys_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/teams","hooks_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/hooks","issue_events_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/issues/events{/number}","events_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/events","assignees_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/assignees{/user}","branches_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/branches{/branch}","tags_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/tags","blobs_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/git/refs{/sha}","trees_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/statuses/{sha}","languages_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/languages","stargazers_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/stargazers","contributors_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/contributors","subscribers_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/subscribers","subscription_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/subscription","commits_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/commits{/sha}","git_commits_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/git/commits{/sha}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/comments{/number}","issue_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/issues/comments/{number}","contents_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/contents/{+path}","compare_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/merges","archive_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/downloads","issues_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/issues{/number}","pulls_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls{/number}","milestones_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/milestones{/number}","notifications_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/labels{/name}","releases_url":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/releases{/id}","created_at":"2014-05-06T06:18:00Z","updated_at":"2014-10-30T16:15:20Z","pushed_at":"2014-10-30T18:49:48Z","git_url":"git://github.com/GoogleCloudPlatform/gcloud-node.git","ssh_url":"git@github.com:GoogleCloudPlatform/gcloud-node.git","clone_url":"https://github.com/GoogleCloudPlatform/gcloud-node.git","svn_url":"https://github.com/GoogleCloudPlatform/gcloud-node","homepage":"https://googlecloudplatform.github.io/gcloud-node","size":10389,"stargazers_count":339,"watchers_count":339,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":39,"mirror_url":null,"open_issues_count":20,"forks":39,"open_issues":20,"watchers":339,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264"},"html":{"href":"https://github.com/GoogleCloudPlatform/gcloud-node/pull/264"},"issue":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/issues/264"},"comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/issues/264/comments"},"review_comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264/comments"},"review_comment":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/pulls/264/commits"},"statuses":{"href":"https://api.github.com/repos/GoogleCloudPlatform/gcloud-node/statuses/5ad83714ac70188e6c71833e3d6c0e70fc01064b"}}}}