diff --git a/lib/table.js b/lib/table.js index 5a114686..be3505e9 100644 --- a/lib/table.js +++ b/lib/table.js @@ -1,4 +1,3 @@ -// istanbul ignore file 'use strict'; var isArray = require('lodash/isArray'); diff --git a/test/create.test.js b/test/create.test.js index 28f92dcc..931f333f 100644 --- a/test/create.test.js +++ b/test/create.test.js @@ -4,16 +4,18 @@ var testHelpers = require('./test_helpers'); describe('record creation', function() { var airtable; + var testExpressApp; var teardownAsync; - beforeAll(function() { + beforeEach(function() { return testHelpers.getMockEnvironmentAsync().then(function(env) { airtable = env.airtable; + testExpressApp = env.testExpressApp; teardownAsync = env.teardownAsync; }); }); - afterAll(function() { + afterEach(function() { return teardownAsync(); }); @@ -51,6 +53,35 @@ describe('record creation', function() { ); }); + it('can throw an error if create fails', function(done) { + testExpressApp.set('handler override', function(req, res) { + res.status(402).json({ + error: {message: 'foo bar'}, + }); + }); + + return airtable + .base('app123') + .table('Table') + .create( + { + foo: 'boo', + bar: 'yar', + }, + {typecast: true} + ) + .then( + function() { + throw new Error('Promise unexpectly fufilled.'); + }, + function(err) { + expect(err.statusCode).toBe(402); + expect(err.message).toBe('foo bar'); + done(); + } + ); + }); + it('can add the "typecast" parameter when creating one record', function() { return airtable .base('app123') diff --git a/test/delete.test.js b/test/delete.test.js index 517679a0..8ec98016 100644 --- a/test/delete.test.js +++ b/test/delete.test.js @@ -4,16 +4,18 @@ var testHelpers = require('./test_helpers'); describe('record deletion', function() { var airtable; + var testExpressApp; var teardownAsync; - beforeAll(function() { + beforeEach(function() { return testHelpers.getMockEnvironmentAsync().then(function(env) { airtable = env.airtable; + testExpressApp = env.testExpressApp; teardownAsync = env.teardownAsync; }); }); - afterAll(function() { + afterEach(function() { return teardownAsync(); }); @@ -50,6 +52,29 @@ describe('record deletion', function() { }); }); + it('can throw an error if delete fails', function(done) { + testExpressApp.set('handler override', function(req, res) { + res.status(402).json({ + error: {message: 'foo bar'}, + }); + }); + + return airtable + .base('app123') + .table('Table') + .destroy(['rec123', 'rec456']) + .then( + function() { + throw new Error('Promise unexpectly fufilled.'); + }, + function(err) { + expect(err.statusCode).toBe(402); + expect(err.message).toBe('foo bar'); + done(); + } + ); + }); + it('can delete multiple records and call a callback', function(done) { airtable .base('app123') diff --git a/test/find.test.js b/test/find.test.js new file mode 100644 index 00000000..92fe0db9 --- /dev/null +++ b/test/find.test.js @@ -0,0 +1,44 @@ +'use strict'; + +var testHelpers = require('./test_helpers'); + +describe('record retrival', function() { + var airtable; + var testExpressApp; + var teardownAsync; + + beforeAll(function() { + return testHelpers.getMockEnvironmentAsync().then(function(env) { + airtable = env.airtable; + testExpressApp = env.testExpressApp; + teardownAsync = env.teardownAsync; + }); + }); + + afterAll(function() { + return teardownAsync(); + }); + + it('can find one record', function() { + var recordId = 'record1'; + + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table/record1?'); + res.json({ + id: req.params.recordId, + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }); + }); + + return airtable + .base('app123') + .table('Table') + .find(recordId) + .then(function(foundRecord) { + expect(foundRecord.id).toBe(recordId); + expect(foundRecord.get('Name')).toBe('Rebecca'); + }); + }); +}); diff --git a/test/list.test.js b/test/list.test.js new file mode 100644 index 00000000..2c9e5746 --- /dev/null +++ b/test/list.test.js @@ -0,0 +1,283 @@ +'use strict'; + +var testHelpers = require('./test_helpers'); + +describe('list records', function() { + var airtable; + var testExpressApp; + var teardownAsync; + + beforeAll(function() { + return testHelpers.getMockEnvironmentAsync().then(function(env) { + airtable = env.airtable; + testExpressApp = env.testExpressApp; + teardownAsync = env.teardownAsync; + }); + }); + + afterAll(function() { + return teardownAsync(); + }); + + it('lists records with a limit and offset without opts', function(done) { + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table/?limit=50&offset=offset000'); + res.json({ + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + offset: 'offsetABC', + }); + }); + + return airtable + .base('app123') + .table('Table') + .list(50, 'offset000', function(err, records, offset) { + expect(err).toBeNull(); + expect(records.length).toBe(1); + expect(records[0].getId()).toBe('recordA'); + expect(records[0].get('Name')).toBe('Rebecca'); + expect(offset).toBe('offsetABC'); + done(); + }); + }); + + it('lists records with a limit, offset, and opts', function(done) { + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe( + '/v0/app123/Table/?limit=50&offset=offset000&otherOptions=getpassedalong' + ); + res.json({ + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + offset: 'offsetABC', + }); + }); + + return airtable + .base('app123') + .table('Table') + .list(50, 'offset000', {otherOptions: 'getpassedalong'}, function( + err, + records, + offset + ) { + expect(err).toBeNull(); + expect(records.length).toBe(1); + expect(records[0].getId()).toBe('recordA'); + expect(records[0].get('Name')).toBe('Rebecca'); + expect(offset).toBe('offsetABC'); + done(); + }); + }); + + it('can throw an error if list fails', function(done) { + testExpressApp.set('handler override', function(req, res) { + res.status(402).json({ + error: {message: 'foo bar'}, + }); + }); + + return airtable + .base('app123') + .table('Table') + .list(50, 'offset000', function(err, records, offset) { + expect(err.statusCode).toBe(402); + expect(err.message).toBe('foo bar'); + expect(records).toBeUndefined(); + expect(offset).toBeUndefined(); + done(); + }); + }); + + it('iterates through each record with opts', function(done) { + var iterationCounter = 0; + + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table/?limit=100&opts=arepassedalong'); + res.json({ + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + { + id: 'recordB', + fields: {Name: 'Mike'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + }); + }); + + return airtable + .base('app123') + .table('Table') + .forEach( + {opts: 'arepassedalong'}, + function(record) { + if (iterationCounter === 0) { + expect(record.getId()).toBe('recordA'); + expect(record.get('Name')).toBe('Rebecca'); + } else if (iterationCounter === 1) { + expect(record.getId()).toBe('recordB'); + expect(record.get('Name')).toBe('Mike'); + } + iterationCounter++; + }, + function() { + expect(iterationCounter).toBe(2); + done(); + } + ); + }); + + it('iterates through each record without opts', function(done) { + var iterationCounter = 0; + var json = { + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + }; + + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table/?limit=100'); + res.json(json); + }); + + return airtable + .base('app123') + .table('Table') + .forEach( + function(record) { + expect(record.getId()).toBe('recordA'); + expect(record.get('Name')).toBe('Rebecca'); + iterationCounter++; + }, + function() { + expect(iterationCounter).toBe(1); + done(); + } + ); + }); + + it('iterates through records without any records', function(done) { + var iterationCounter = 0; + var json = { + records: [], + }; + + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table/?limit=100'); + res.json(json); + }); + + return airtable + .base('app123') + .table('Table') + .forEach( + function() { + iterationCounter++; + }, + function() { + expect(iterationCounter).toBe(0); + done(); + } + ); + }); + + it('can throw an error if forEach fails', function(done) { + testExpressApp.set('handler override', function(req, res) { + res.status(402).json({ + error: {message: 'foo bar'}, + }); + }); + + return airtable + .base('app123') + .table('Table') + .forEach( + function() {}, + function(err) { + expect(err.statusCode).toBe(402); + expect(err.message).toBe('foo bar'); + done(); + } + ); + }); + + it('iterates through each record and handles pagination', function(done) { + var iterationCounter = 0; + + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table/?limit=100'); + res.json({ + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + offset: 'offset123', + }); + }); + + return airtable + .base('app123') + .table('Table') + .forEach( + function(record) { + if (iterationCounter === 0) { + expect(record.getId()).toBe('recordA'); + expect(record.get('Name')).toBe('Rebecca'); + + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table/?limit=100&offset=offset123'); + // Don't include an offset in second page of results + // to indicate that it is the final page of results + res.json({ + records: [ + { + id: 'recordB', + fields: {Name: 'Mike'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + }); + }); + } else if (iterationCounter === 1) { + expect(record.getId()).toBe('recordB'); + expect(record.get('Name')).toBe('Mike'); + } + iterationCounter++; + }, + function() { + expect(iterationCounter).toBe(2); + done(); + } + ); + }); +}); diff --git a/test/select.test.js b/test/select.test.js new file mode 100644 index 00000000..6657e3ce --- /dev/null +++ b/test/select.test.js @@ -0,0 +1,129 @@ +'use strict'; + +var testHelpers = require('./test_helpers'); + +describe('record selection', function() { + var airtable; + var testExpressApp; + var teardownAsync; + + beforeAll(function() { + return testHelpers.getMockEnvironmentAsync().then(function(env) { + airtable = env.airtable; + testExpressApp = env.testExpressApp; + teardownAsync = env.teardownAsync; + }); + }); + + afterAll(function() { + return teardownAsync(); + }); + + it('selects records without params', function(done) { + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table?'); + res.json({ + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + offset: 'offsetABC', + }); + }); + + return airtable + .base('app123') + .table('Table') + .select() + .eachPage(function page(records) { + expect(records.length).toBe(1); + records.forEach(function(record) { + expect(record.id).toBe('recordA'); + expect(record.get('Name')).toBe('Rebecca'); + }); + done(); + }); + }); + + it('selects records with valid params', function(done) { + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe( + '/v0/app123/Table?maxRecords=50&sort%5B0%5D%5Bfield%5D=Name&sort%5B0%5D%5Bdirection%5D=desc' + ); + res.json({ + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + offset: 'offsetABC', + }); + }); + + return airtable + .base('app123') + .table('Table') + .select({maxRecords: 50, sort: [{field: 'Name', direction: 'desc'}]}) + .eachPage(function page(records) { + records.forEach(function(record) { + expect(record.id).toBe('recordA'); + expect(record.get('Name')).toBe('Rebecca'); + }); + done(); + }); + }); + + it('selects records filters out invalid parameters and extra arguments without erroring', function(done) { + testExpressApp.set('handler override', function(req, res) { + expect(req.method).toBe('GET'); + expect(req.url).toBe('/v0/app123/Table?'); + res.json({ + records: [ + { + id: 'recordA', + fields: {Name: 'Rebecca'}, + createdTime: '2020-04-20T16:20:00.000Z', + }, + ], + offset: 'offsetABC', + }); + }); + + return airtable + .base('app123') + .table('Table') + .select({ignoredParam: 'ignore me'}, {anotherIgnored: 'param'}) + .eachPage(function page(records) { + records.forEach(function(record) { + expect(record.id).toBe('recordA'); + expect(record.get('Name')).toBe('Rebecca'); + }); + done(); + }); + }); + + it('selects records errors on invalid parameters', function() { + return expect(() => { + airtable + .base('app123') + .table('Table') + .select({maxRecords: 'should not be a string'}); + }).toThrow(/`maxRecords` should be a number/); + }); + + it('selects records errors when the params are not a plain object', function() { + return expect(() => { + airtable + .base('app123') + .table('Table') + .select('?invalid=params'); + }).toThrow(/should be a plain object/); + }); +}); diff --git a/test/table.test.js b/test/table.test.js new file mode 100644 index 00000000..bc2a90fc --- /dev/null +++ b/test/table.test.js @@ -0,0 +1,25 @@ +'use strict'; + +var testHelpers = require('./test_helpers'); + +describe('Table', function() { + var airtable; + var teardownAsync; + + beforeAll(function() { + return testHelpers.getMockEnvironmentAsync().then(function(env) { + airtable = env.airtable; + teardownAsync = env.teardownAsync; + }); + }); + + afterAll(function() { + return teardownAsync(); + }); + + it('throws an error without a table reference', function() { + return expect(() => { + airtable.base('app123').table(); + }).toThrow(); + }); +}); diff --git a/test/update.test.js b/test/update.test.js index 3f089481..2a9cbf6b 100644 --- a/test/update.test.js +++ b/test/update.test.js @@ -4,16 +4,18 @@ var testHelpers = require('./test_helpers'); describe('record updates', function() { var airtable; + var testExpressApp; var teardownAsync; - beforeAll(function() { + beforeEach(function() { return testHelpers.getMockEnvironmentAsync().then(function(env) { airtable = env.airtable; + testExpressApp = env.testExpressApp; teardownAsync = env.teardownAsync; }); }); - afterAll(function() { + afterEach(function() { return teardownAsync(); }); @@ -163,6 +165,38 @@ describe('record updates', function() { expect(updatedRecords[1].get('typecasted')).toBe(true); }); }); + + it('can throw an error if update fails', function(done) { + testExpressApp.set('handler override', function(req, res) { + res.status(402).json({ + error: {message: 'foo bar'}, + }); + }); + + return airtable + .base('app123') + .table('Table') + .update([ + { + id: 'rec123', + fields: {foo: 'boo'}, + }, + { + id: 'rec456', + fields: {bar: 'yar'}, + }, + ]) + .then( + function() { + throw new Error('Promise unexpectly fufilled.'); + }, + function(err) { + expect(err.statusCode).toBe(402); + expect(err.message).toBe('foo bar'); + done(); + } + ); + }); }); describe('destructive updates', function() {