Skip to content

Commit b1f78a5

Browse files
committed
Get all the tests passing
- Fix issues with concurrency tests - Update readme - Support limit, skip, sort - Fix minor typos in tests - Support upsert - Base64 decode the key to get past bash expansion issues in Travis config - Add firestore to read and write tests - Implement the clearAll function
1 parent c405b32 commit b1f78a5

File tree

5 files changed

+103
-36
lines changed

5 files changed

+103
-36
lines changed

.travis.yml

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ before_install:
1313
- tar -xzf /tmp/dynamodb_local_latest.tar.gz -C /tmp
1414
- java -Djava.library.path=/tmp/DynamoDBLocal_lib -jar /tmp/DynamoDBLocal.jar -inMemory &
1515
- sleep 2
16+
- echo $BASE64_GOOGLE_KEY | base64 --decode > /tmp/firestore-node-viewmodel-key.json
1617

1718
services:
1819
- mongodb
@@ -40,3 +41,4 @@ env:
4041
- AWS_SECRET_ACCESS_KEY=SECRET
4142
- AWS_REGION=us-east-1
4243
- AWS_DYNAMODB_ENDPOINT=http://localhost:8000
44+
- GOOGLE_APPLICATION_CREDENTIALS=/tmp/firestore-node-viewmodel-key.json

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,11 @@ Simple (equality comparison) find queries are supported by passing javascript ob
340340
});
341341
```
342342
343-
The queryOptions parameter is also ignored.
343+
The queryOptions parameter supports limit, skip, and sort, in a mongoDb-like syntax.
344344
345+
### Testing Setup
346+
347+
To provide the authentication file to tests, the `GOOGLE_APPLICATION_CREDENTIALS` environment setting should point to the file so it can be loaded by firestore. To inject the file in TravisCI, create a new environment variable called `BASE64_GOOGLE_KEY` in the Travis GUI, and set the value of this to be the Base64 encoded content of the file. The .travis.yml file contains configuration to decode this setting and write it out to a known location for the CI settings to pickup.
345348
346349
# [Release notes](https://github.com/adrai/node-viewmodel/blob/master/releasenotes.md)
347350

lib/databases/firestore.js

+88-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var util = require('util'),
22
_ = require('lodash'),
3+
async = require('async'),
34
ConcurrencyError = require('../concurrencyError'),
45
gcFirestore = require('@google-cloud/firestore'),
56
Repository = require('../base'),
@@ -37,6 +38,59 @@ function firestoreQueryParser(collectionRef, queryParams) {
3738
return _.reduce(params, function(acc, q) {
3839
return acc.where.apply(acc, q);
3940
}, collectionRef);
41+
};
42+
43+
function emptyCollection(db, collection, callback) {
44+
var collectionRef = db.collection(collection);
45+
var query = collectionRef.get().then(function (querySnapshot) {
46+
var writeBatch = db.batch();
47+
querySnapshot.forEach(function (documentSnapshot) {
48+
var documentPath = collection + '/' + documentSnapshot.id;
49+
var documentRef = db.doc(documentPath);
50+
writeBatch.delete(documentRef);
51+
});
52+
writeBatch.commit().then(function () {
53+
if (callback) callback(null);
54+
});
55+
});
56+
};
57+
58+
function getPrecondition(vm) {
59+
var precondition = {};
60+
if (!_.isUndefined(vm.get('_updateTime'))) {
61+
const time = vm.get('_updateTime');
62+
if (_.isDate(time)) {
63+
precondition['lastUpdateTime'] = time.toISOString();
64+
} else if (_.isString(time)) {
65+
precondition['lastUpdateTime'] = time;
66+
}
67+
}
68+
return precondition;
69+
}
70+
71+
function enrichVMWithTimestamps(vm, documentSnapshot) {
72+
_.isUndefined(documentSnapshot.readTime) ? false : vm.set('_readTime', documentSnapshot.readTime);
73+
_.isUndefined(documentSnapshot.createTime) ? false : vm.set('_createTime', documentSnapshot.createTime);
74+
_.isUndefined(documentSnapshot.updateTime) ? false : vm.set('_updateTime', documentSnapshot.updateTime);
75+
return vm;
76+
};
77+
78+
function applyQueryOptions(query, options) {
79+
if (!_.isUndefined(options)) {
80+
// Apply supported queryOptions
81+
if (_.has(options, 'limit')) {
82+
query = query.limit(options.limit);
83+
}
84+
if (_.has(options, 'skip')) {
85+
query = query.offset(options.skip);
86+
}
87+
if (_.has(options, 'sort')) {
88+
var sortKey = options.sort.keys[0];
89+
var direction = options.sort.keys[sortKey] == 1 ? 'asc' : 'desc';
90+
query = query.orderBy(sortKey, direction);
91+
}
92+
}
93+
return query;
4094
}
4195

4296
_.extend(Firestore.prototype, {
@@ -82,6 +136,7 @@ _.extend(Firestore.prototype, {
82136

83137
documentRef.get().then(function (documentSnapshot) {
84138
var vm = new ViewModel(documentSnapshot.data() || { id }, self);
139+
vm = enrichVMWithTimestamps(vm, documentSnapshot);
85140
if (documentSnapshot.exists) {
86141
vm.actionOnCommit = 'update';
87142
} else {
@@ -92,17 +147,18 @@ _.extend(Firestore.prototype, {
92147
},
93148

94149
find: function (queryParams, queryOptions, callback) {
95-
// NOTE: queryOptions is ignored
96-
97150
this.checkConnection();
98151

99152
var self = this;
100153
var collectionRef = this.db.collection(this.collection);
101154

102155
var query = firestoreQueryParser(collectionRef, queryParams);
156+
query = applyQueryOptions(query, queryOptions);
157+
103158
query.get().then(function (querySnapshot) {
104159
var vms = _.map(querySnapshot.docs, function(documentSnapshot) {
105160
var vm = new ViewModel(documentSnapshot.data(), self);
161+
vm = enrichVMWithTimestamps(vm, documentSnapshot);
106162
vm.actionOnCommit = 'update';
107163
return vm;
108164
});
@@ -118,12 +174,15 @@ _.extend(Firestore.prototype, {
118174
var collectionRef = this.db.collection(this.collection);
119175

120176
var query = firestoreQueryParser(collectionRef, queryParams);
177+
_.unset(queryOptions, 'limit');
178+
query = applyQueryOptions(query, queryOptions);
121179
query.limit(1).get().then(function (querySnapshot) {
122180
if (querySnapshot.size == 0) {
123181
callback(null, null);
124182
}
125183
querySnapshot.forEach(function (documentSnapshot) {
126184
var vm = new ViewModel(documentSnapshot.data(), self);
185+
vm = enrichVMWithTimestamps(vm, documentSnapshot);
127186
vm.actionOnCommit = 'update';
128187
callback(null, vm);
129188
});
@@ -135,12 +194,17 @@ _.extend(Firestore.prototype, {
135194

136195
if (!vm.actionOnCommit) return callback(new Error('actionOnCommit is not defined!'));
137196

197+
var self = this;
198+
138199
switch(vm.actionOnCommit) {
139200
case 'delete':
140201
var documentPath = this.collection + '/' + vm.id;
141202
var documentRef = this.db.doc(documentPath);
142-
documentRef.delete().then(function () {
203+
var precondition = getPrecondition(vm);
204+
documentRef.delete(precondition).then(function () {
143205
callback(null);
206+
}).catch(function (err) {
207+
return callback(new ConcurrencyError());
144208
});
145209
break;
146210
case 'create':
@@ -161,12 +225,23 @@ _.extend(Firestore.prototype, {
161225
var documentRef = this.db.doc(documentPath);
162226
documentRef.get().then(function (documentSnapshot) {
163227
if (!documentSnapshot.exists) {
164-
return callback(new ConcurrencyError());
228+
documentRef.set(vm.attributes).then(function () {
229+
vm.actionOnCommit = 'update';
230+
callback(null, vm);
231+
});
232+
} else {
233+
if (!_.isUndefined(documentSnapshot.updateTime) &&
234+
_.isUndefined(vm.get('_updateTime'))) {
235+
return callback(new ConcurrencyError());
236+
}
237+
238+
var precondition = getPrecondition(vm);
239+
documentRef.update(vm.attributes, precondition).then(function () {
240+
self.get(vm.id, callback);
241+
}, function (err) {
242+
return callback(new ConcurrencyError());
243+
});
165244
}
166-
documentRef.update(vm.attributes).then(function () {
167-
vm.actionOnCommit = 'update';
168-
callback(null, vm);
169-
});
170245
});
171246
break;
172247
default:
@@ -196,27 +271,14 @@ _.extend(Firestore.prototype, {
196271
return;
197272
}
198273

199-
var collectionRef = this.db.collection(this.collection);
200-
var query = collectionRef.get().then(function (querySnapshot) {
201-
var writeBatch = self.db.batch();
202-
querySnapshot.forEach(function (documentSnapshot) {
203-
var documentPath = self.collection + '/' + documentSnapshot.id;
204-
var documentRef = self.db.doc(documentPath);
205-
writeBatch.delete(documentRef);
206-
});
207-
writeBatch.commit().then(function () {
208-
if (callback) callback(null);
209-
});
210-
});
274+
emptyCollection(this.db, this.collection, callback);
211275
},
212276

213-
/**
214-
* NEVER USE THIS FUNCTION!!! ONLY FOR TESTS!
215-
* clears the complete store...
216-
* @param {Function} callback the function that will be called when this action has finished [optional]
217-
*/
218277
clearAll: function (callback) {
219-
implementError(callback);
278+
var self = this;
279+
async.each(collections, function (col, callback) {
280+
emptyCollection(self.db, col, callback);
281+
}, callback);
220282
},
221283

222284
});

test/repositoryReadTest.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('Repository read', function() {
7777

7878
describe('with options containing a type property with the value of', function() {
7979

80-
var types = ['inmemory', 'mongodb', 'tingodb', 'couchdb', 'redis', 'elasticsearch6', 'dynamodb'/*, 'elasticsearch', 'documentdb', 'azuretable'*/];
80+
var types = ['inmemory', 'mongodb', 'tingodb', 'couchdb', 'redis', 'elasticsearch6', 'dynamodb' /*, 'elasticsearch', 'documentdb', 'azuretable', 'firestore' */];
8181

8282
types.forEach(function(type) {
8383

@@ -490,7 +490,7 @@ describe('Repository read', function() {
490490

491491
});
492492

493-
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb'];
493+
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb', 'firestore'];
494494

495495
if (!_.includes(noQueryArray, type)) {
496496

@@ -781,7 +781,7 @@ describe('Repository read', function() {
781781

782782
});
783783

784-
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb'];
784+
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb', 'firestore'];
785785

786786
if (!_.includes(noQueryArray, type)) {
787787

test/repositoryWriteTest.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe.only('Repository write', function() {
7676

7777
describe('with options containing a type property with the value of', function() {
7878

79-
var types = ['inmemory', 'mongodb', 'tingodb', 'couchdb', 'redis', 'elasticsearch6', 'dynamodb'/*, 'elasticsearch', 'documentdb', 'azuretable'*/];
79+
var types = ['inmemory', 'mongodb', 'tingodb', 'couchdb', 'redis', 'elasticsearch6', 'dynamodb' /*, 'elasticsearch', 'documentdb', 'azuretable', 'firestore'*/];
8080

8181
types.forEach(function(type) {
8282

@@ -482,7 +482,7 @@ describe.only('Repository write', function() {
482482

483483
});
484484

485-
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb'];
485+
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb', 'firestore'];
486486

487487
if (!_.includes(noQueryArray, type)) {
488488

@@ -1247,7 +1247,7 @@ describe.only('Repository write', function() {
12471247

12481248
});
12491249

1250-
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb'];
1250+
var noQueryArray = ['azuretable', 'documentdb', 'dynamodb', 'firestore'];
12511251

12521252
if (!_.includes(noQueryArray, type)) {
12531253

@@ -1510,7 +1510,7 @@ describe.only('Repository write', function() {
15101510

15111511
});
15121512

1513-
describe('but beeing updated by someone else in the meantime', function() {
1513+
describe('but being updated by someone else in the meantime', function() {
15141514

15151515
it('it should callback with a concurrency error', function(done) {
15161516

@@ -1536,7 +1536,7 @@ describe.only('Repository write', function() {
15361536

15371537
});
15381538

1539-
describe('but beeing updated by someone else in the meantime and creating with the same id', function() {
1539+
describe('but being updated by someone else in the meantime and creating with the same id', function() {
15401540

15411541
it('it should callback with a concurrency error', function(done) {
15421542

@@ -1605,7 +1605,7 @@ describe.only('Repository write', function() {
16051605

16061606
});
16071607

1608-
describe('but beeing updated by someone else in the meantime', function() {
1608+
describe('but being updated by someone else in the meantime', function() {
16091609

16101610
it('it should callback with a concurrency error', function(done) {
16111611

0 commit comments

Comments
 (0)