firebase / firebase-js-sdk

Firebase Javascript SDK
https://firebase.google.com/docs/web/setup
Other
4.85k stars 892 forks source link

@firebase/testing Firestore GeoPoint is not compatible with "firebase-admin" GeoPoint. #3659

Closed bradleymackey closed 4 years ago

bradleymackey commented 4 years ago

[REQUIRED] Describe your environment

[REQUIRED] Describe the problem

Two seemingly identical GeoPoint types are provided from firebase-admin (for production) and @firebase/testing (for testing). The two GeoPoints are not compatible - we can't put a firebase-admin GeoPoint in a @firebase/testing database and vice versa.

Trying to place a firebase-admin GeoPoint into a test Firestore database created with @firebase/testing results in the error:

Error [FirebaseError]: Function DocumentReference.set() called with invalid data. Unsupported field value: a custom object (found in field home in document some/location)

A bit of manual type introspection (with util.inspect) reveals that the internal structure of these GeoPoint types differs, but ideally these would be compatible. For example, consider a GeoPoint at latitude 10, longitude 10.

@firebase/testing Geopoint:

GeoPoint { _lat: 10, _long: 10 }

firebase-admin GeoPoint:

GeoPoint { _latitude: 10, _longitude: 10 }

This is an issue because it means that existing models and functions that we would like to test (that only accept firebase-admin GeoPoints) will fail when we try to add them to the database.

Steps to reproduce:

Create a test Firestore database with @firebase/testing.

Try to insert a GeoPoint created from firebase-admin into the test database. The operation fails with the error message provided above.

Relevant Code:

import * as firebase from "@firebase/testing";
import {
    firestore as firebaseAdminFirestore
} from "firebase-admin";

import "mocha";
import {
    assert
} from "chai";

const makeDocReferencesIfNeeded = (data) => {
    for (const docPath in data) {
        const doc = data[docPath];
        for (const key in doc) {
            const value = doc[key];
            if (typeof value === "string" && value.startsWith("path:/")) {
                doc[key] = current.admin.doc(value.substr(5));
            }
        }
    }
};

describe("GeoPoint", async () => {

    it("can set a 'firebase-admin' geopoint in the database", async () => {
        const admin = firebase.initializeAdminApp({
            projectId: "some-project-id",
        });
        const db = admin.firestore();

        const geopoint = new firebaseAdminFirestore.GeoPoint(10, 10);
        const util = require("util");
        console.log(util.inspect(geopoint));

        const mockDatabase = {};
        mockDatabase["some/location"] = {
            home: geopoint,
        };
        makeDocReferencesIfNeeded(mockDatabase);
        try {
            for (const key in mockDatabase) {
                // all we need is to set data without error
                const ref = admin.doc(key);
                await ref.set(mockDatabase[key]);
            }
            assert.ok(true);
        } catch (error) {
            console.error(error);
            assert.fail();
        }
    });

});
thebrianchen commented 4 years ago

Apologies for the confusion, but the firebase-admin and @firebase/testing types are not meant to be compatible. See discussion and how to adjust your tests in a prior issue on this topic.

The TL;DR on this is:

The underlying problem is that @firebase/testing is based on the Web SDK, but you're exercising the Admin SDK. Though these interfaces look similar they're actually different implementations and have incompatible types.

bradleymackey commented 4 years ago

Thanks for the info, this is unfortunate that this is the case though. It makes testing security rules with existing TypeScript data models a tad more difficult.