diff --git a/functions/src/utils.js b/functions/src/utils.js index 498cbf6..cb84633 100644 --- a/functions/src/utils.js +++ b/functions/src/utils.js @@ -1,17 +1,26 @@ const config = require("./config.js"); const mapValue = (value) => { - if (typeof value === "object" && value !== null && value.seconds != null && value.nanoseconds != null) { + const isObject = typeof value === 'object'; + const notNull = value !== null; + const length = Object.keys(value).length; + + const latitude = value.latitude ?? value.lat; + const longitude = value.longitude ?? value.lng; + const hasGeohashField = value.geohash != null && length == 3; + const isGeopointType = latitude != null && longitude != null && (length == 2 || hasGeohashField); + + if (isObject && notNull && value.seconds != null && value.nanoseconds != null) { // convert date to Unix timestamp // https://typesense.org/docs/0.22.2/api/collections.html#indexing-dates return Math.floor(value.toDate().getTime() / 1000); - } else if (typeof value === "object" && value !== null && value.latitude != null && value.longitude != null) { - return [value.latitude, value.longitude]; - } else if (typeof value === "object" && value !== null && value.firestore != null && value.path != null) { + } else if (isObject && notNull && isGeopointType) { + return [latitude, longitude]; + } else if (isObject && notNull && value.firestore != null && value.path != null) { return {"path": value.path}; } else if (Array.isArray(value)) { return value.map(mapValue); - } else if (typeof value === "object" && value !== null) { + } else if (isObject && notNull) { return Object.fromEntries(Object.entries(value).map(([key, value]) => [key, mapValue(value)])); } else { return value; diff --git a/test/utils.spec.js b/test/utils.spec.js index 11ece6d..aa05da1 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -1,45 +1,105 @@ -const test = require("firebase-functions-test")({ +const test = require('firebase-functions-test')({ projectId: process.env.GCLOUD_PROJECT, }); -describe("Utils", () => { - describe("typesenseDocumentFromSnapshot", () => { - describe("when document fields are mentioned explicitly", () => { - it("returns a Typesense document with only the specified fields", async () => { - const typesenseDocumentFromSnapshot = - (await import("../functions/src/utils.js")).typesenseDocumentFromSnapshot; +describe('Utils', () => { + describe('typesenseDocumentFromSnapshot', () => { + describe('when document fields are mentioned explicitly', () => { + it('returns a Typesense document with only the specified fields', async () => { + const typesenseDocumentFromSnapshot = ( + await import('../functions/src/utils.js') + ).typesenseDocumentFromSnapshot; - const documentSnapshot = test.firestore.makeDocumentSnapshot({ - author: "Author X", - title: "Title X", - country: "USA", - }, "id"); + const documentSnapshot = test.firestore.makeDocumentSnapshot( + { + author: 'Author X', + title: 'Title X', + country: 'USA', + }, + 'id' + ); const result = await typesenseDocumentFromSnapshot(documentSnapshot); expect(result).toEqual({ - id: "id", - author: "Author X", - title: "Title X", + id: 'id', + author: 'Author X', + title: 'Title X', }); }); }); - describe("when no fields are mentioned explicitly", () => { - it("returns a Typesense document with all fields", async () => { - const typesenseDocumentFromSnapshot = - (await import("../functions/src/utils.js")).typesenseDocumentFromSnapshot; - const documentSnapshot = test.firestore.makeDocumentSnapshot({ - author: "Author X", - title: "Title X", - country: "USA", - }, "id"); + describe('when no fields are mentioned explicitly', () => { + it('returns a Typesense document with all fields', async () => { + const typesenseDocumentFromSnapshot = ( + await import('../functions/src/utils.js') + ).typesenseDocumentFromSnapshot; - const result = await typesenseDocumentFromSnapshot(documentSnapshot, []); + const documentSnapshot = test.firestore.makeDocumentSnapshot( + { + author: 'Author X', + title: 'Title X', + country: 'USA', + }, + 'id' + ); + + const result = await typesenseDocumentFromSnapshot( + documentSnapshot, + [] + ); + expect(result).toEqual({ + id: 'id', + author: 'Author X', + title: 'Title X', + country: 'USA', + }); + }); + }); + + it('Can parse geopoint datatype', async () => { + const typesenseDocumentFromSnapshot = ( + await import('../functions/src/utils.js') + ).typesenseDocumentFromSnapshot; + const data = [ + { + location: { + latitude: 1, + longitude: 2, + }, + }, + { + location: { + lat: 1, + lng: 2, + }, + }, + { + location: { + geohash: 'abc', + latitude: 1, + longitude: 2, + }, + }, + { + location: { + geohash: 'abc', + lat: 1, + lng: 2, + }, + }, + ]; + data.forEach(async (item) => { + const documentSnapshot = test.firestore.makeDocumentSnapshot( + item, + 'id' + ); + const result = await typesenseDocumentFromSnapshot( + documentSnapshot, + [] + ); expect(result).toEqual({ - id: "id", - author: "Author X", - title: "Title X", - country: "USA", + id: 'id', + location: [1, 2], }); }); });