@@ -21,6 +21,127 @@ const COLLECTION = 'second-database';
2121const SECOND_DATABASE_ID = 'second-rnfb' ;
2222
2323describe ( 'Second Database' , function ( ) {
24+ describe . only ( 'getFirestore (same default app, multiple databaseIds)' , function ( ) {
25+ describe ( 'modular' , function ( ) {
26+ beforeEach ( async function ( ) {
27+ await wipe ( false , '(default)' ) ;
28+ await wipe ( false , SECOND_DATABASE_ID ) ;
29+ } ) ;
30+
31+ it ( 'returns distinct Firestore instances per databaseId (customUrlOrRegion)' , function ( ) {
32+ const { getApp } = modular ;
33+ const { getFirestore } = firestoreModular ;
34+ const app = getApp ( ) ;
35+ const dbDefault = getFirestore ( app , '(default)' ) ;
36+ const dbSecond = getFirestore ( app , SECOND_DATABASE_ID ) ;
37+
38+ dbDefault . app . name . should . equal ( dbSecond . app . name ) ;
39+ dbDefault . customUrlOrRegion . should . equal ( '(default)' ) ;
40+ dbSecond . customUrlOrRegion . should . equal ( SECOND_DATABASE_ID ) ;
41+ } ) ;
42+
43+ // Probes Android native cache in UniversalFirebaseFirestoreCommon (must key by app:databaseId).
44+ // Uses the same emulator DB ids and wipe pattern as the rest of this directory.
45+ it ( 'isolates reads and writes per databaseId on Android' , async function ( ) {
46+ if ( ! Platform . android ) {
47+ return ;
48+ }
49+ const { getApp } = modular ;
50+ const { getFirestore, doc, setDoc, getDoc, deleteDoc } = firestoreModular ;
51+
52+ const app = getApp ( ) ;
53+ const dbDefault = getFirestore ( app , '(default)' ) ;
54+ const dbSecond = getFirestore ( app , SECOND_DATABASE_ID ) ;
55+
56+ const pathDefault = 'firestore/multiDbIdentityProbe' ;
57+ const pathSecond = `${ COLLECTION } /multiDbIdentityProbe` ;
58+
59+ await setDoc ( doc ( dbDefault , pathDefault ) , { marker : 'default-db' } ) ;
60+ await setDoc ( doc ( dbSecond , pathSecond ) , { marker : 'second-db' } ) ;
61+
62+ ( await getDoc ( doc ( dbDefault , pathDefault ) ) ) . data ( ) . marker . should . eql ( 'default-db' ) ;
63+ ( await getDoc ( doc ( dbSecond , pathSecond ) ) ) . data ( ) . marker . should . eql ( 'second-db' ) ;
64+
65+ await deleteDoc ( doc ( dbDefault , pathDefault ) ) ;
66+ await deleteDoc ( doc ( dbSecond , pathSecond ) ) ;
67+ } ) ;
68+
69+ // The broken map never stores entries under the composite firestoreKey, so CACHE_HIT on that key
70+ // does not happen from getFirestoreForApp alone — simple read/write often still goes through
71+ // FirebaseFirestore.getInstance and looks correct. These cases stress other paths: non-thread-safe
72+ // WeakHashMap under parallel native calls, and terminate() which tries to remove(firestoreKey) but
73+ // never matches entries stored under appName only.
74+ it ( 'stress: many parallel writes to both databases (Android)' , async function ( ) {
75+ if ( ! Platform . android ) {
76+ return ;
77+ }
78+ const { getApp } = modular ;
79+ const { getFirestore, doc, setDoc, getDoc, deleteDoc } = firestoreModular ;
80+ const app = getApp ( ) ;
81+ const dbDefault = getFirestore ( app , '(default)' ) ;
82+ const dbSecond = getFirestore ( app , SECOND_DATABASE_ID ) ;
83+
84+ // Two path segments each: rules allow `firestore/{document=**}` and
85+ // `second-database/{document=**}` when database is second-rnfb. Deeper paths like
86+ // `second-database/cacheStress/0` are not valid document paths (odd segment count).
87+ const n = 25 ;
88+ const batch = [ ] ;
89+ for ( let i = 0 ; i < n ; i ++ ) {
90+ batch . push (
91+ setDoc ( doc ( dbDefault , `firestore/cacheStressDoc_${ i } ` ) , { db : 'default' , i } ) ,
92+ setDoc ( doc ( dbSecond , `${ COLLECTION } /cacheStressDoc_${ i } ` ) , { db : 'second' , i } ) ,
93+ ) ;
94+ }
95+ await Promise . all ( batch ) ;
96+
97+ for ( let i = 0 ; i < n ; i ++ ) {
98+ ( await getDoc ( doc ( dbDefault , `firestore/cacheStressDoc_${ i } ` ) ) ) . data ( ) . db . should . eql (
99+ 'default' ,
100+ ) ;
101+ ( await getDoc ( doc ( dbSecond , `${ COLLECTION } /cacheStressDoc_${ i } ` ) ) ) . data ( ) . db . should . eql (
102+ 'second' ,
103+ ) ;
104+ }
105+
106+ for ( let i = 0 ; i < n ; i ++ ) {
107+ await deleteDoc ( doc ( dbDefault , `firestore/cacheStressDoc_${ i } ` ) ) ;
108+ await deleteDoc ( doc ( dbSecond , `${ COLLECTION } /cacheStressDoc_${ i } ` ) ) ;
109+ }
110+ } ) ;
111+
112+ it ( 'terminate then reconnect both databases still isolates data (Android)' , async function ( ) {
113+ if ( ! Platform . android ) {
114+ return ;
115+ }
116+ const { getApp } = modular ;
117+ const { getFirestore, doc, setDoc, getDoc, deleteDoc, terminate } = firestoreModular ;
118+
119+ const app = getApp ( ) ;
120+ let dbDefault = getFirestore ( app , '(default)' ) ;
121+ let dbSecond = getFirestore ( app , SECOND_DATABASE_ID ) ;
122+
123+ await terminate ( dbDefault ) ;
124+ await terminate ( dbSecond ) ;
125+
126+ dbDefault = getFirestore ( app , '(default)' ) ;
127+ dbSecond = getFirestore ( app , SECOND_DATABASE_ID ) ;
128+
129+ await setDoc ( doc ( dbDefault , 'firestore/afterTerminateProbe' ) , { tag : 'default' } ) ;
130+ await setDoc ( doc ( dbSecond , `${ COLLECTION } /afterTerminateProbe` ) , { tag : 'second' } ) ;
131+
132+ ( await getDoc ( doc ( dbDefault , 'firestore/afterTerminateProbe' ) ) ) . data ( ) . tag . should . eql (
133+ 'default' ,
134+ ) ;
135+ ( await getDoc ( doc ( dbSecond , `${ COLLECTION } /afterTerminateProbe` ) ) ) . data ( ) . tag . should . eql (
136+ 'second' ,
137+ ) ;
138+
139+ await deleteDoc ( doc ( dbDefault , 'firestore/afterTerminateProbe' ) ) ;
140+ await deleteDoc ( doc ( dbSecond , `${ COLLECTION } /afterTerminateProbe` ) ) ;
141+ } ) ;
142+ } ) ;
143+ } ) ;
144+
24145 describe ( 'firestore().collection().where()' , function ( ) {
25146 describe ( 'v8 compatibility' , function ( ) {
26147 let firestore ;
0 commit comments