mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
260 lines
8.5 KiB
TypeScript
260 lines
8.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2017 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 { newQueryForPath } from '../../../src/core/query';
|
|
import { TimerId } from '../../../src/util/async_queue';
|
|
import { Code } from '../../../src/util/error';
|
|
import { doc, query } from '../../util/helpers';
|
|
|
|
import { describeSpec, specTest } from './describe_spec';
|
|
import { spec } from './spec_builder';
|
|
|
|
describeSpec('Offline:', [], () => {
|
|
specTest('Empty queries are resolved if client goes offline', [], () => {
|
|
const query1 = query('collection');
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.expectEvents(query1, {
|
|
fromCache: true,
|
|
hasPendingWrites: false
|
|
})
|
|
// no further events
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
);
|
|
});
|
|
|
|
specTest('A successful message delays offline status', [], () => {
|
|
const query1 = query('collection');
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
.watchAcks(query1)
|
|
// first error triggers unknown state
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
// second error triggers offline state
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.expectEvents(query1, {
|
|
fromCache: true,
|
|
hasPendingWrites: false
|
|
})
|
|
// no further events
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
);
|
|
});
|
|
|
|
specTest(
|
|
'Removing all listeners delays "Offline" status on next listen',
|
|
['eager-gc'],
|
|
'Marked as no-lru because when a listen is re-added, it gets a new target id rather than ' +
|
|
'reusing one',
|
|
() => {
|
|
const query1 = query('collection');
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
// error triggers offline state
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.expectEvents(query1, {
|
|
fromCache: true,
|
|
hasPendingWrites: false
|
|
})
|
|
// Remove listen.
|
|
.userUnlistens(query1)
|
|
// If the next (already scheduled) connection attempt fails, we'll move
|
|
// to unknown since there are no listeners, and stop trying to connect.
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
// Suppose sometime later we listen again, it should take one failure
|
|
// before we get cached data.
|
|
.userListens(query1)
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.expectEvents(query1, {
|
|
fromCache: true,
|
|
hasPendingWrites: false
|
|
})
|
|
);
|
|
}
|
|
);
|
|
|
|
specTest('Queries revert to fromCache=true when offline.', [], () => {
|
|
const query1 = query('collection');
|
|
const docA = doc('collection/a', 1000, { key: 'a' });
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
.watchAcksFull(query1, 1000, docA)
|
|
.expectEvents(query1, { added: [docA] })
|
|
// first error triggers unknown state
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.restoreListen(query1, 'resume-token-1000')
|
|
// second error triggers offline state and fromCache: true
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.expectEvents(query1, { fromCache: true })
|
|
// Going online and getting a CURRENT message triggers fromCache: false
|
|
.watchAcksFull(query1, 1000)
|
|
.expectEvents(query1, { fromCache: false })
|
|
);
|
|
});
|
|
|
|
specTest('Queries with limbo documents handle going offline.', [], () => {
|
|
const query1 = query('collection');
|
|
const docA = doc('collection/a', 1000, { key: 'a' });
|
|
const limboQuery = newQueryForPath(docA.key.path);
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
.watchAcksFull(query1, 1000, docA)
|
|
.expectEvents(query1, { added: [docA] })
|
|
.watchResets(query1)
|
|
// No more documents
|
|
.watchCurrents(query1, 'resume-token-1001')
|
|
.watchSnapshots(1001)
|
|
// docA will now be in limbo (triggering fromCache=true)
|
|
.expectLimboDocs(docA.key)
|
|
.expectEvents(query1, { fromCache: true })
|
|
// first error triggers unknown state
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.restoreListen(query1, 'resume-token-1001')
|
|
// second error triggers offline state.
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.watchAcksFull(query1, 1001)
|
|
.watchAcksFull(limboQuery, 1001)
|
|
// Limbo document is resolved. No longer from cache.
|
|
.expectEvents(query1, { removed: [docA], fromCache: false })
|
|
.expectLimboDocs()
|
|
);
|
|
});
|
|
|
|
specTest('OnlineState timeout triggers offline behavior', [], () => {
|
|
const query1 = query('collection');
|
|
const docA = doc('collection/a', 1000, { key: 'a' });
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
|
|
// OnlineState timer should trigger offline behavior (fromCache=true).
|
|
.runTimer(TimerId.OnlineStateTimeout)
|
|
.expectEvents(query1, {
|
|
fromCache: true
|
|
})
|
|
|
|
// We should get no further events for failed connection attempts.
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
|
|
// We should get events after a successful connection.
|
|
.watchAcksFull(query1, 1000, docA)
|
|
.expectEvents(query1, { added: [docA], fromCache: false })
|
|
|
|
// Running timers should have no effect now.
|
|
.runTimer(TimerId.All)
|
|
|
|
// After a disconnect, the timer should become active again.
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.restoreListen(query1, 'resume-token-1000')
|
|
.runTimer(TimerId.OnlineStateTimeout)
|
|
.expectEvents(query1, {
|
|
fromCache: true
|
|
})
|
|
);
|
|
});
|
|
|
|
specTest(
|
|
'New queries return immediately with fromCache=true when offline due to ' +
|
|
'stream failures.',
|
|
[],
|
|
() => {
|
|
const query1 = query('collection');
|
|
const query2 = query('collection2');
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
// After failure, we mark the client offline and trigger an empty
|
|
// fromCache event.
|
|
.watchStreamCloses(Code.UNAVAILABLE)
|
|
.expectEvents(query1, { fromCache: true })
|
|
|
|
// A new query should immediately return from cache.
|
|
.userListens(query2)
|
|
.expectEvents(query2, { fromCache: true })
|
|
);
|
|
}
|
|
);
|
|
|
|
specTest(
|
|
'New queries return immediately with fromCache=true when offline due to ' +
|
|
'OnlineState timeout.',
|
|
[],
|
|
() => {
|
|
const query1 = query('collection');
|
|
const query2 = query('collection2');
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
.runTimer(TimerId.OnlineStateTimeout)
|
|
.expectEvents(query1, { fromCache: true })
|
|
|
|
// A new query should immediately return from cache.
|
|
.userListens(query2)
|
|
.expectEvents(query2, { fromCache: true })
|
|
);
|
|
}
|
|
);
|
|
|
|
// TODO(b/114055812): This shouldn't really need to be marked eager-gc
|
|
specTest(
|
|
'Queries return from cache when network disabled',
|
|
['eager-gc'],
|
|
() => {
|
|
const query1 = query('collection');
|
|
return (
|
|
spec()
|
|
.disableNetwork()
|
|
.userListens(query1)
|
|
.expectEvents(query1, { fromCache: true })
|
|
.userUnlistens(query1)
|
|
|
|
// There was once a bug where removing the last listener accidentally
|
|
// reverted us to OnlineState.Unknown, so make sure it works a second time
|
|
.userListens(query1)
|
|
.expectEvents(query1, { fromCache: true })
|
|
.userUnlistens(query1)
|
|
);
|
|
}
|
|
);
|
|
|
|
specTest('Client stays offline during credential change', [], () => {
|
|
// Reproduces a bug that caused the client to switch to OnlineState
|
|
// `Unknown` during a credential change.
|
|
|
|
const query1 = query('collection');
|
|
return (
|
|
spec()
|
|
.disableNetwork()
|
|
.changeUser('user1')
|
|
.userListens(query1)
|
|
// Client is still offline and we raise a `fromCache` event immediately
|
|
.expectEvents(query1, {
|
|
fromCache: true
|
|
})
|
|
);
|
|
});
|
|
});
|