mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
208 lines
7.7 KiB
TypeScript
208 lines
7.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { expect } from 'chai';
|
|
|
|
import { FirestoreError } from '../../../src';
|
|
import { DEFAULT_TRANSACTION_OPTIONS } from '../../../src/core/transaction_options';
|
|
import { TimerId } from '../../../src/util/async_queue';
|
|
import { Deferred } from '../../util/promise';
|
|
import {
|
|
collection,
|
|
doc,
|
|
getDoc,
|
|
runTransaction,
|
|
setDoc,
|
|
TransactionOptions
|
|
} from '../util/firebase_export';
|
|
import { apiDescribe, withTestDb } from '../util/helpers';
|
|
import { asyncQueue } from '../util/internal_helpers';
|
|
|
|
apiDescribe('Database transactions (with internal API)', persistence => {
|
|
it('should increment transactionally', async () => {
|
|
// A set of concurrent transactions.
|
|
const transactionPromises: Array<Promise<void>> = [];
|
|
const readPromises: Array<Promise<void>> = [];
|
|
// A barrier to make sure every transaction reaches the same spot.
|
|
const barrier = new Deferred<void>();
|
|
let started = 0;
|
|
|
|
await withTestDb(persistence, async db => {
|
|
asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry);
|
|
const docRef = doc(collection(db, 'counters'));
|
|
await setDoc(docRef, { count: 5 });
|
|
// Make 3 transactions that will all increment.
|
|
for (let i = 0; i < 3; i++) {
|
|
const resolveRead = new Deferred<void>();
|
|
readPromises.push(resolveRead.promise);
|
|
transactionPromises.push(
|
|
runTransaction(db, async transaction => {
|
|
const snapshot = await transaction.get(docRef);
|
|
expect(snapshot).to.exist;
|
|
started += 1;
|
|
resolveRead.resolve();
|
|
await barrier.promise;
|
|
transaction.set(docRef, {
|
|
count: snapshot.data()!['count'] + 1
|
|
});
|
|
})
|
|
);
|
|
}
|
|
|
|
// Let all of the transactions fetch the old value and stop once.
|
|
await Promise.all(readPromises);
|
|
|
|
// Let all of the transactions continue and wait for them to
|
|
// finish.
|
|
expect(started).to.equal(3);
|
|
barrier.resolve();
|
|
await Promise.all(transactionPromises);
|
|
|
|
// Now all transaction should be completed, so check the result.
|
|
const snapshot = await getDoc(docRef);
|
|
expect(snapshot).to.exist;
|
|
expect(snapshot.data()!['count']).to.equal(8);
|
|
});
|
|
});
|
|
|
|
it('should update transactionally', async () => {
|
|
// A set of concurrent transactions.
|
|
const transactionPromises: Array<Promise<void>> = [];
|
|
const readPromises: Array<Promise<void>> = [];
|
|
// A barrier to make sure every transaction reaches the same spot.
|
|
const barrier = new Deferred<void>();
|
|
let counter = 0;
|
|
|
|
await withTestDb(persistence, async db => {
|
|
asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry);
|
|
const docRef = doc(collection(db, 'counters'));
|
|
await setDoc(docRef, {
|
|
count: 5,
|
|
other: 'yes'
|
|
});
|
|
// Make 3 transactions that will all increment.
|
|
for (let i = 0; i < 3; i++) {
|
|
const resolveRead = new Deferred<void>();
|
|
readPromises.push(resolveRead.promise);
|
|
transactionPromises.push(
|
|
runTransaction(db, async transaction => {
|
|
const snapshot = await transaction.get(docRef);
|
|
expect(snapshot).to.exist;
|
|
counter += 1;
|
|
resolveRead.resolve();
|
|
await barrier.promise;
|
|
await transaction.update(docRef, {
|
|
count: snapshot.data()!['count'] + 1
|
|
});
|
|
})
|
|
);
|
|
}
|
|
|
|
// Let all of the transactions fetch the old value and stop once.
|
|
await Promise.all(readPromises);
|
|
|
|
// Let all of the transactions continue and wait for them to
|
|
// finish. There should be 3 initial transaction runs.
|
|
expect(counter).to.equal(3);
|
|
barrier.resolve();
|
|
await Promise.all(transactionPromises);
|
|
|
|
// Now all transaction should be completed, so check the result.
|
|
// There should be a maximum of 3 retries: once for the 2nd update,
|
|
// and twice for the 3rd update.
|
|
expect(counter).to.be.lessThan(7);
|
|
const snapshot = await getDoc(docRef);
|
|
expect(snapshot).to.exist;
|
|
expect(snapshot.data()!['count']).to.equal(8);
|
|
expect(snapshot.data()!['other']).to.equal('yes');
|
|
});
|
|
});
|
|
|
|
it('should fail transaction (maxAttempts: default) when reading a doc twice with different versions', async () => {
|
|
await withTestDb(persistence, async db => {
|
|
asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry);
|
|
const docRef = doc(collection(db, 'counters'));
|
|
let counter = 0;
|
|
await setDoc(docRef, { count: 15 });
|
|
try {
|
|
await runTransaction(db, async transaction => {
|
|
counter++;
|
|
// Get the docRef once.
|
|
await transaction.get(docRef);
|
|
// Do a write outside of the transaction. Because the transaction
|
|
// will retry, set the document to a different value each time.
|
|
await setDoc(docRef, { count: 1234 + counter });
|
|
// Get the docRef again in the transaction with the new
|
|
// version.
|
|
await transaction.get(docRef);
|
|
// Now try to update the docRef from within the transaction.
|
|
// This should fail, because we read 15 earlier.
|
|
await transaction.set(docRef, { count: 16 });
|
|
});
|
|
expect.fail('transaction should fail');
|
|
} catch (e) {
|
|
const err = e as FirestoreError;
|
|
expect(err).to.exist;
|
|
expect(err.code).to.equal('aborted');
|
|
}
|
|
const snapshot = await getDoc(docRef);
|
|
expect(snapshot.data()!['count']).to.equal(1234 + counter);
|
|
expect(counter).to.equal(DEFAULT_TRANSACTION_OPTIONS.maxAttempts);
|
|
});
|
|
});
|
|
|
|
it('should fail transaction (maxAttempts: 1) when reading a doc twice with different versions', async () => {
|
|
await withTestDb(persistence, async db => {
|
|
asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry);
|
|
const docRef = doc(collection(db, 'counters'));
|
|
const options: TransactionOptions = {
|
|
maxAttempts: 1
|
|
};
|
|
let counter = 0;
|
|
await setDoc(docRef, { count: 15 });
|
|
try {
|
|
await runTransaction(
|
|
db,
|
|
async transaction => {
|
|
counter++;
|
|
// Get the docRef once.
|
|
await transaction.get(docRef);
|
|
// Do a write outside of the transaction. Because the transaction
|
|
// will retry, set the document to a different value each time.
|
|
await setDoc(docRef, { count: 1234 + counter });
|
|
// Get the docRef again in the transaction with the new
|
|
// version.
|
|
await transaction.get(docRef);
|
|
// Now try to update the docRef from within the transaction.
|
|
// This should fail, because we read 15 earlier.
|
|
await transaction.set(docRef, { count: 16 });
|
|
},
|
|
options
|
|
);
|
|
expect.fail('transaction should fail');
|
|
} catch (e) {
|
|
const err = e as FirestoreError;
|
|
expect(err).to.exist;
|
|
expect(err.code).to.equal('aborted');
|
|
}
|
|
const snapshot = await getDoc(docRef);
|
|
expect(snapshot.data()!['count']).to.equal(1234 + counter);
|
|
expect(counter).to.equal(options.maxAttempts);
|
|
});
|
|
});
|
|
});
|