mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
236 lines
6.6 KiB
TypeScript
236 lines
6.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2024 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 { deleteApp, initializeApp, FirebaseApp } from '@firebase/app';
|
|
import { expect, use } from 'chai';
|
|
import chaiAsPromised from 'chai-as-promised';
|
|
import * as sinon from 'sinon';
|
|
|
|
import {
|
|
connectDataConnectEmulator,
|
|
DataConnect,
|
|
executeMutation,
|
|
executeQuery,
|
|
getDataConnect,
|
|
mutationRef,
|
|
QueryRef,
|
|
queryRef,
|
|
QueryResult,
|
|
SerializedRef,
|
|
SOURCE_CACHE,
|
|
SOURCE_SERVER,
|
|
subscribe,
|
|
terminate
|
|
} from '../src';
|
|
import { DataConnectTransportManager } from '../src/network/manager';
|
|
|
|
import { getConnectionConfig, initDatabase, PROJECT_ID } from './util';
|
|
|
|
use(chaiAsPromised);
|
|
|
|
interface Post {
|
|
id: string;
|
|
description: string;
|
|
}
|
|
interface PostListResponse {
|
|
posts: Post[];
|
|
}
|
|
|
|
const SEEDED_DATA = [
|
|
{
|
|
id: crypto.randomUUID(),
|
|
description: 'task 1'
|
|
},
|
|
{
|
|
id: crypto.randomUUID(),
|
|
description: 'task 2'
|
|
}
|
|
];
|
|
const REAL_DATA = SEEDED_DATA.map(obj => ({
|
|
...obj,
|
|
id: obj.id.replace(/-/g, '')
|
|
}));
|
|
|
|
async function deleteDatabase(instance: DataConnect): Promise<void> {
|
|
for (let i = 0; i < SEEDED_DATA.length; i++) {
|
|
const data = SEEDED_DATA[i];
|
|
const ref = mutationRef(instance, 'RemovePost', { id: data.id });
|
|
await executeMutation(ref);
|
|
}
|
|
}
|
|
async function seedDatabase(
|
|
instance: DataConnect,
|
|
testId: string
|
|
): Promise<void> {
|
|
for (let i = 0; i < SEEDED_DATA.length; i++) {
|
|
const data = { ...SEEDED_DATA[i], testId };
|
|
const ref = mutationRef(instance, 'AddPost', data);
|
|
await executeMutation(ref);
|
|
}
|
|
}
|
|
|
|
interface PostVariables {
|
|
testId: string;
|
|
}
|
|
describe('DataConnect Tests', async () => {
|
|
let dc: DataConnect;
|
|
let app: FirebaseApp;
|
|
const TEST_ID = crypto.randomUUID();
|
|
beforeEach(async () => {
|
|
app = initializeApp({
|
|
projectId: PROJECT_ID
|
|
});
|
|
dc = initDatabase();
|
|
await seedDatabase(dc, TEST_ID);
|
|
sinon
|
|
.stub(DataConnectTransportManager.prototype, 'invokeSubscribe')
|
|
.returns();
|
|
sinon
|
|
.stub(DataConnectTransportManager.prototype, 'invokeUnsubscribe')
|
|
.returns();
|
|
});
|
|
afterEach(async () => {
|
|
await deleteDatabase(dc);
|
|
await deleteApp(app);
|
|
await terminate(dc);
|
|
sinon.restore();
|
|
});
|
|
function getPostsRef(): QueryRef<PostListResponse, PostVariables> {
|
|
return queryRef<PostListResponse, PostVariables>(dc, 'ListPosts', {
|
|
testId: TEST_ID
|
|
});
|
|
}
|
|
it('Can get all posts', async () => {
|
|
const taskListQuery = getPostsRef();
|
|
const taskListRes = await executeQuery(taskListQuery);
|
|
expect(taskListRes.data).to.deep.eq({
|
|
posts: REAL_DATA
|
|
});
|
|
});
|
|
it(`instantly executes a query if one hasn't been subscribed to`, async () => {
|
|
const taskListQuery = getPostsRef();
|
|
const promise = new Promise<QueryResult<PostListResponse, PostVariables>>(
|
|
(resolve, reject) => {
|
|
const unsubscribe = subscribe(taskListQuery, {
|
|
onNext: res => {
|
|
unsubscribe();
|
|
resolve(res);
|
|
},
|
|
onErr: () => {
|
|
unsubscribe();
|
|
reject(res);
|
|
}
|
|
});
|
|
}
|
|
);
|
|
const res = await promise;
|
|
expect(res.data).to.deep.eq({
|
|
posts: REAL_DATA
|
|
});
|
|
expect(res.source).to.eq(SOURCE_SERVER);
|
|
});
|
|
it(`returns the result source as cache when data already exists`, async () => {
|
|
const taskListQuery = getPostsRef();
|
|
const queryResult = await executeQuery(taskListQuery);
|
|
const result = await waitForFirstEvent(taskListQuery);
|
|
expect(result.data).to.deep.eq(queryResult.data);
|
|
expect(result.source).to.eq(SOURCE_CACHE);
|
|
});
|
|
it(`returns the proper JSON when calling .toJSON()`, async () => {
|
|
const taskListQuery = getPostsRef();
|
|
await executeQuery(taskListQuery);
|
|
const result = await waitForFirstEvent(taskListQuery);
|
|
const serializedRef: SerializedRef<PostListResponse, PostVariables> = {
|
|
data: {
|
|
posts: REAL_DATA
|
|
},
|
|
fetchTime: Date.now().toLocaleString(),
|
|
refInfo: {
|
|
connectorConfig: {
|
|
...getConnectionConfig(),
|
|
projectId: PROJECT_ID
|
|
},
|
|
name: taskListQuery.name,
|
|
variables: { testId: TEST_ID }
|
|
},
|
|
source: SOURCE_CACHE
|
|
};
|
|
const json = result.toJSON();
|
|
expect(json.data).to.deep.eq(serializedRef.data);
|
|
expect(json.refInfo).to.deep.eq(serializedRef.refInfo);
|
|
expect(json.source).to.deep.eq(serializedRef.source);
|
|
});
|
|
it(`throws an error when the user can't connect to the server`, async () => {
|
|
// You can't point an existing data connect instance to a new emulator port, so we have to create a new one
|
|
const fakeInstance = getDataConnect({
|
|
connector: 'wrong',
|
|
location: 'wrong',
|
|
service: 'wrong'
|
|
});
|
|
connectDataConnectEmulator(fakeInstance, 'localhost', 3512);
|
|
const taskListQuery = queryRef<PostListResponse>(fakeInstance, 'ListPosts');
|
|
await expect(executeQuery(taskListQuery)).to.eventually.be.rejectedWith(
|
|
/EADDRNOTAVAIL|ECONNREFUSED|Failed to fetch/
|
|
);
|
|
});
|
|
it('throws an error with just the message when the server responds with an error', async () => {
|
|
const invalidTaskListQuery = queryRef<PostListResponse>(
|
|
dc,
|
|
'UnauthorizedQuery'
|
|
);
|
|
const message =
|
|
'unauthorized: you are not authorized to perform this operation';
|
|
await expect(
|
|
executeQuery(invalidTaskListQuery)
|
|
).to.eventually.be.rejectedWith(message);
|
|
});
|
|
});
|
|
async function waitForFirstEvent<Data, Variables>(
|
|
query: QueryRef<Data, Variables>
|
|
): Promise<QueryResult<Data, Variables>> {
|
|
return new Promise<{
|
|
result: QueryResult<Data, Variables>;
|
|
unsubscribe: () => void;
|
|
}>((resolve, reject) => {
|
|
const onResult: (result: QueryResult<Data, Variables>) => void = (
|
|
result: QueryResult<Data, Variables>
|
|
) => {
|
|
setTimeout(() => {
|
|
resolve({
|
|
result,
|
|
unsubscribe
|
|
});
|
|
});
|
|
};
|
|
const unsubscribe = subscribe(query, {
|
|
onNext: onResult,
|
|
onErr: e => {
|
|
reject({ e, unsubscribe });
|
|
}
|
|
});
|
|
}).then(
|
|
({ result, unsubscribe }) => {
|
|
unsubscribe();
|
|
return result;
|
|
},
|
|
({ e, unsubscribe }) => {
|
|
unsubscribe();
|
|
throw e;
|
|
}
|
|
);
|
|
}
|