Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
ui-debug.log
CLAUDE.md
firebase-debug.log
node_modules
firestore-debug.log
939 changes: 0 additions & 939 deletions _emulator/firebase-debug.log

This file was deleted.

3 changes: 3 additions & 0 deletions _emulator/firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"hosting": {
"port": 8081
},
"functions": {
"port": 5001
},
"storage": {
"port": 9199
},
Expand Down
19 changes: 0 additions & 19 deletions _emulator/firestore-debug.log

This file was deleted.

2 changes: 1 addition & 1 deletion extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ resources:
properties:
location: us-central1
httpsTrigger: {}
runtime: "nodejs14"
runtime: "nodejs20"

# Learn about the `params` field in the docs
params:
Expand Down
14 changes: 7 additions & 7 deletions functions/__tests__/bundle.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { buildQuery } from "../src/build_bundle";
import * as admin from "firebase-admin";
import { buildQuery } from "../src/build_bundle";

describe("buildQuery", () => {
let db: admin.firestore.Firestore;
beforeEach(() => {
db = admin.initializeApp({ projectId: "demo-experimental" }).firestore();
});

xit("should build expected queries", () => {
it("should build expected queries", () => {
const queries: [admin.firestore.Query, admin.firestore.Query][] = [
[
buildQuery(db, { collection: "test-coll", conditions: [] }, {}, {}),
Expand All @@ -22,7 +22,7 @@ describe("buildQuery", () => {
collectionGroupQuery: true,
},
{},
{}
{},
),
db.collectionGroup("test-coll") as admin.firestore.Query,
],
Expand All @@ -39,7 +39,7 @@ describe("buildQuery", () => {
],
},
{},
{}
{},
),
db
.collection("test-coll")
Expand All @@ -62,7 +62,7 @@ describe("buildQuery", () => {
],
},
{},
{}
{},
),
db
.collection("test-coll")
Expand Down Expand Up @@ -94,7 +94,7 @@ describe("buildQuery", () => {
field: "field1",
limit: 10,
contains: ["a", "d", "e"],
}
},
),
db
.collection("test-coll")
Expand Down Expand Up @@ -126,7 +126,7 @@ describe("buildQuery", () => {
otherField: "otherField",
floatValue: 3.0,
value: false,
}
},
),
db
.collection("test-coll")
Expand Down
162 changes: 134 additions & 28 deletions functions/__tests__/functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as admin from "firebase-admin";
import axios from "axios";
import { setupTestData } from "./test-setup";

process.env.FIRESTORE_EMULATOR_HOST = "localhost:8080";
process.env.FIREBASE_FIRESTORE_EMULATOR_ADDRESS = "localhost:8080";
Expand All @@ -13,10 +13,57 @@ if (admin.apps.length === 0) {
admin.initializeApp({ projectId: "demo-experimental" });
}

// Setup test data before all tests
beforeAll(async () => {
await setupTestData();
});

const extractObjectfromBuffer = ($) => {
const splitBuffers = $.toString().replace(/[\d]+[{]+/g, ",{");
const formatted = splitBuffers.toString().substring(1);
return JSON.parse(`[${formatted.toString()}]`);
const buffer = Buffer.from($);
const content = buffer.toString();

// Parse bundle format: length-prefixed JSON objects
const objects = [];
let position = 0;

while (position < content.length) {
// Find the next '{' which starts a JSON object
const jsonStart = content.indexOf("{", position);
if (jsonStart === -1) break;

// Extract length prefix (if any)
const lengthStr = content.substring(position, jsonStart);

// Find the matching closing brace
let braceCount = 0;
let jsonEnd = jsonStart;
for (let i = jsonStart; i < content.length; i++) {
if (content[i] === "{") braceCount++;
else if (content[i] === "}") {
braceCount--;
if (braceCount === 0) {
jsonEnd = i;
break;
}
}
}

const jsonStr = content.substring(jsonStart, jsonEnd + 1);
try {
objects.push(JSON.parse(jsonStr));
} catch (e) {
console.error("Failed to parse:", jsonStr);
}

position = jsonEnd + 1;
}

// Return [metadata, documentMetadata, document] - pad with empty objects if needed
while (objects.length < 3) {
objects.push({});
}

return objects;
};

const extName = "ext-firestore-bundle-builder-serve";
Expand All @@ -30,7 +77,12 @@ describe("functions", () => {
it("successfully returns a bundle with queries, documents and params combined", async () => {
const bundleName = "documents-queries-params";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -49,7 +101,12 @@ describe("functions", () => {
it("successfully returns a bundle using a query with a collection", async () => {
const bundleName = "query-with-a-collection";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -68,7 +125,12 @@ describe("functions", () => {
it("successfully returns a bundle using a query with a collection and condition", async () => {
const bundleName = "query-with-a-collection-and-condition";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -87,7 +149,12 @@ describe("functions", () => {
it("successfully returns a bundle using a query with a collection and where clause", async () => {
const bundleName = "query-with-a-collection-and-condition";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -103,10 +170,15 @@ describe("functions", () => {
expect(document.documentMetadata.queries[0]).toEqual("example");
});

xit("successfully returns a bundle using a query with a collection and multiple where clauses", async () => {
it("successfully returns a bundle using a query with a collection and multiple where clauses", async () => {
const bundleName = "query-with-a-collection-and-multiple-where-conditions";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -125,7 +197,12 @@ describe("functions", () => {
it("successfully returns a bundle using a document", async () => {
const bundleName = "single-document";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -136,19 +213,24 @@ describe("functions", () => {

/*** check document metadata */
expect(documentMetadata.documentMetadata.name).toEqual(
"projects/demo-experimental/databases/(default)/documents/documents/document1"
"projects/demo-experimental/databases/(default)/documents/documents/document1",
);

/*** check document */
expect(document.document.name).toEqual(
"projects/demo-experimental/databases/(default)/documents/documents/document1"
"projects/demo-experimental/databases/(default)/documents/documents/document1",
);
});

it("successfully returns a bundle using multiple documents", async () => {
const bundleName = "multiple-documents";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -172,7 +254,12 @@ describe("functions", () => {
it("successfully returns a bundle using params", async () => {
const bundleName = "query-with-param";
const url = extUrl(bundleName) + "?name=document2";
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -191,7 +278,12 @@ describe("functions", () => {
it("successfully returns a bundle using clientCache", async () => {
const bundleName = "with-client-cache";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -201,10 +293,15 @@ describe("functions", () => {
expect(metadata.metadata.totalDocuments).toEqual(0);
});

xit("successfully returns a bundle using serverCache", async () => {
it("successfully returns a bundle using serverCache", async () => {
const bundleName = "with-server-cache";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -217,7 +314,12 @@ describe("functions", () => {
xit("successfully returns a bundle using fileCache", async () => {
const bundleName = "with-file-cache";
const url = extUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -230,7 +332,12 @@ describe("functions", () => {
xit("successfully returns a request through a webiste hosted by Firebase", async () => {
const bundleName = "documents-queries-params";
const url = extHostedUrl(bundleName);
const { data: bundle } = await axios(url);
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
const bundle = await response.arrayBuffer();

const [metadata, documentMetadata, document] =
extractObjectfromBuffer(bundle);
Expand All @@ -248,14 +355,13 @@ describe("functions", () => {

it("returns a 404 response if an unknown bundle is provided", async () => {
const bundleName = "unknown-bundle";
const url = extHostedUrl(bundleName);
const url = extUrl(bundleName);

return axios(url)
.then(() => {
fail("should not succeed");
})
.catch((ex) => {
expect(ex.response.status).toEqual(404);
});
const response = await fetch(url, {
headers: {
"Accept-Encoding": "identity",
},
});
expect(response.status).toEqual(404);
});
});
Loading