Skip to content

Can't use Timestamp in Firestore rules unit tests #6077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ebeloded opened this issue Mar 17, 2022 · 18 comments
Open

Can't use Timestamp in Firestore rules unit tests #6077

ebeloded opened this issue Mar 17, 2022 · 18 comments
Assignees

Comments

@ebeloded
Copy link

ebeloded commented Mar 17, 2022

[REQUIRED] Describe your environment

  • Operating System version: Mac OS
  • Firebase SDK version: "firebase": "9.6.8", "firebase-admin": "10.0.2", "@firebase/rules-unit-testing": "2.0.2",
  • Firebase Product: Firestore

[REQUIRED] Describe the problem

Steps to reproduce:

While writing unit tests for Firestore rules, I am trying to set a timestamp on a document, but this results in the following error:

  • for serverTimestamp():

'Function DocumentReference.set() called with invalid data. Unsupported field value: a custom ServerTimestampFieldValueImpl object (found in field timestamp in document users/new-user)

  • for Timestamp.now() and for adminFirebase.firestore.Timestamp.now():

'Function DocumentReference.set() called with invalid data. Unsupported field value: a custom Timestamp object (found in field timestamp in document users/new-user)'

Relevant Code:

import { serverTimestamp, Timestamp } from 'firebase/firestore'
import firebase from 'firebase-admin'

async function test() {
  const testEnv = await initializeTestEnvironment({
    //...
  })

  testEnv.withSecurityRulesDisabled(async (ctx) => {
    // I tried three ways of generating the timestamp, none of them worked:
    const timestamp = serverTimestamp()
    // const timestamp = firebase.firestore.Timestamp.now()
    // const timestamp = Timestamp.now()

    await ctx.firestore().doc('users/new-user').set({ timestamp })
  })
}

Update

It turns out that I can provide new Date() as timestamp, which doesn't throw an error. However, the timestamp generated in this case doesn't pass the security rule that verifies that the time:

allow create: request.resource.data.timestamp == request.time
@schmidt-sebastian
Copy link
Contributor

@ebeloded Can you run npm list to see if you have two different versions of the Firebase SDK?

@ebeloded
Copy link
Author

ebeloded commented Mar 17, 2022

This is what gets listed:

@firebase/rules-unit-testing 2.0.2
firebase 9.6.8
firebase-admin 10.0.2

I've noticed that Timestamp returned by firebase-admin is different from the one returned from firebase/firestore. The admin library generates object with same fields, but starting with an underscore.

BTW, the error only happens in tests. When doing the same in the app (against Emulator or not), everything works and the time checking rule is respected.

@schmidt-sebastian
Copy link
Contributor

Yes, the Timestamp object are unfortunately not compatible. Are you able to use the Timestamp from firebase-admin (firebase.firestore.Timestamp)?

@ebeloded
Copy link
Author

No, Timestamp from the admin library doesn't work either.

I've tried serverTimestamp() and Timestamp.now() from client library, as well as adminFirebase.firestore.Timestamp.now() from firebase-admin. Nothing worked.

Furthermore, I tried another FieldValue (arrayUnion), and it didn't work either.

@schultek
Copy link

I'm having the same issue.

@harshmandan
Copy link

Having the same issue.

@laurentpayot
Copy link

laurentpayot commented Mar 28, 2022

@ebeloded I had this issue a long time ago. With Jest, I am mocking firestore.serverTimestamp() as a workaround. If I remember well that was needed to make the rules pass when using new Date() as mentioned in your update.

The TypeScript code below should do that, but I’m using my own client compatibility layer of firestore-admin mocks instead of the jest.requireActual() line. So I cannot guarantee it will work as jest.requireActual() does not work with ESM. So this code is just here to give you the general idea 😉 Hope it will help…

import type { Timestamp } from '@firebase/firestore-types'

// ...

    jest.unstable_mockModule('@firebase/firestore', () => {
        return {
            ...<any>jest.requireActual('@firebase/firestore'),
            serverTimestamp: jest.fn(() => <Timestamp><unknown>new Date())
        }
    })

@ebeloded
Copy link
Author

Thanks @laurentpayot for pointing out the possible workaround. The problem is that this doesn't work if the Firestore rules are checking timestamp integrity. Like this:

allow create: request.resource.data.timestamp == request.time

This rule won't work unless you use proper serverTimestamp in the code. Using Date results in a slight difference in timestamp values.

@schmidt-sebastian schmidt-sebastian removed their assignment Apr 14, 2022
@juyaki
Copy link

juyaki commented May 12, 2022

I had the same problem, and was able to fix it by not using firebase/firestore and firebase-admin.
You have to use the firebase from firebase/compat

A very simple example:

import * as firebaseTest from "@firebase/rules-unit-testing"
import firebase from "firebase/compat"

describe ('some test') {
    test ('that we can use serverTimestamp') {
        const env = await firebaseTest.initializeTestEnvironment({ projectId: 'your-project-id' })
        const context  = env.authenticatedContext('some-user-id')
        const firestore = context.firestore()
        await firestore.collection('collectionId').doc('docId').set({
            createdAt: firebase.firestore.FieldValue.serverTimestamp()
        })
    }
}

In that case, even with the below security rule your test will pass.

allow create: request.resource.data.createdAt == request.time

I think it's the same for Timestamp and other firebase types. Take it all from firebase/compat when writing your tests.

@dconeybe dconeybe self-assigned this May 17, 2022
@dconeybe
Copy link
Contributor

Hello all. I just want to acknowledge that we are still investigating this issue internally. I'll have a more concrete response by the end of the week.

@avolkovi avolkovi self-assigned this May 17, 2022
@dconeybe dconeybe removed their assignment May 18, 2022
@avolkovi
Copy link
Contributor

Hi all, Firebaser here.

@juyaki is correct in #6077 (comment), the underlying issue is that rules-unit-testing is depending entirely on the compat bindings. We will work on finding some time to migrate this library to the modular v9 bindings in the next major version change, in the meantime if we are happy to accept PRs.

@new-carrot
Copy link

I tried serverTimestamp as in the example at https://github.com/firebase/quickstart-testing/blob/master/unit-test-security-rules-v9/test/firestore.spec.js. It works, it can pass a rule with timestamp, for example request.resource.data.updatedAt == request.time

@laurentpayot
Copy link

laurentpayot commented Jun 6, 2022

UPDATE: I’m now using Vitest instead of Jest so I could not use Jest mocks anymore.
Fortunately @schmidt-sebastian’s proposed solution worked for me. I use it like so:

import { Timestamp } from 'firebase-admin/firestore'

function getServerTimestamp() {
    const datesMs = new Date().getTime()
    return new Timestamp(Math.floor(datesMs / 1000), (datesMs % 1000) * 1000)
}

@vpusher
Copy link

vpusher commented Feb 27, 2023

Importing the type from the firestore client library does the trick :

import {Timestamp} from '@firebase/firestore';

Out of curiosity, how come admin and client Timestamp types are not compatible ? Is this on purpose ?

@natedx
Copy link

natedx commented Mar 13, 2023

Turns out we have the same issue, not just in tests but when writing documents to Firestore.
Following the official instructions here we should be able to use set in order to add a Timestamp to Firestore.

This works without the converter, but if we use a converter we get the same error as the author :

Uncaught (in promise) FirebaseError: Function setDoc() called with invalid data (via `toFirestore()`). Unsupported field value: a custom nt object (found in field dateExample in document collection/123456)

I found that nt is the minified name of Timestamp from our installed dependency.

If I instead use a Date, then the file is written correctly with no error. Additionally, if i use serverTimestamp() it works too.

Therefore, I believe that this is an issue where you cannot use Timestamps in return value from toFirestore in a FirestoreDataConverter.

Current workaround for me : Just use Date object instead, we don't need nanosecond precision. It does however make it confusing to read our Converters : we send Date objects and receive Timestamp objects...

@kimamil
Copy link

kimamil commented Apr 6, 2023

I think I am facing the same issue ...

I have an app that uses @firebase/firestore and stores records using Timestamp fields. In a Cloud Function for Firebase I am using the admin SDK @firebase-admin/firestore to do some db cleansing tasks based on those timestamps.

While working with the Timestamps in the app works as expected, in my Function I only receive an empty object for every Timestamp field in my record.

Any thoughts and/or help on this?

@joshwash
Copy link

joshwash commented Nov 8, 2023

Some additional information I ran into while validating an object as a timestamp instance:
FirebaseFirestore.Timestamp class defines required readonly 'seconds' and 'nanoseconds' properties while the Actual timestamp object contains the properties '_seconds' and '_nanoseconds'.

_ is an oldschool way to point out properties like that, but the type should match the implementation either way.

@Vav4ik
Copy link

Vav4ik commented Dec 16, 2023

I came across same problem to test security rules for timestamps. This is what worked for me:
import { serverTimestamp } from "firebase/firestore";
and use it where it's needed, like:

await assertSucceeds(
     testDoc.set({
       email: "[email protected]",
       createdAt: serverTimestamp(),
       lastUpdate: serverTimestamp(),
       name: null,
     })
   );

Simple as that!
You're welcome :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests