🔥

firebase/rules-unit-testingでTimestampが保存できなくて困った話

2021/02/21に公開

こんなエラーが出た

Value for argument "data" is not a valid Firestore document. Detected an object of type "Timestamp" that doesn't match the expected instance (found in field "dateTime"). Please ensure that the Firestore types you are using are from the same NPM package.)

あれ?

const dateTime = firebase.firestore.Timestamp.fromDate(new Date())

でfirestoreに投げたらTimestampに変換してくれるんじゃないの??
使ってるNPM packageが同じかどうか確認してくれだって?

結論 - admin sdkとclient sdkに互換性は無い

  1. admin sdkとclient sdkに互換性は無い
  2. admin sdkでfirestoreに保存しようとしているのに、timestampはclient sdkで生成している

原因はadmin sdkとclient sdkを両方使っているのに区別が出来ていないことでした。

下記の2つのtimestampは互換性が無いため、admin sdkを使って保存しているのに、client sdkのtimestampをfieldに保存しようとすると最初のエラーに遭遇します。

import * as admin from 'firebase-admin'
import * as firebase from '@firebase/rules-unit-testing';

const adminTimestamp = admin.firestore.Timestamp.fromDate(new Date());
const clientTimestamp = firebase.firestore.Timestamp.fromDate(new Date());

私の場合

私の場合は、下記の記事のような方法で初期データを登録していたので、初期データ登録時は「admin sdk」を用いて保存していました。

firebase/rules-unit-testingで初期データを登録したいけどPERMISSION_DENIEDが出る

しかし、timestampは client sdk の方を使っていたわけです。

解決策 - sdk毎のtimestampを生成する関数を作ろう!

今回テストコードを書くために時間の項目のテストが必要だったので、こんな関数を作って対策しました。
この関数は分をプラスすることと、sdkを指定する事を行っているだけなので用途によって書き換える必要があります。

import * as admin from 'firebase-admin'
import * as firebase from '@firebase/rules-unit-testing';

const makeTimestamp = (type: 'client' | 'admin', minute: number) => {
  const dt = new Date();
  // 現在時刻から分をプラスする
  dt.setMinutes(dt.getMinutes() + minute);
  switch (type) {
    case 'admin':
      return admin.firestore.Timestamp.fromDate(dt);
    case 'client':
      return firebase.firestore.Timestamp.fromDate(dt)
  }
}

使い方

// admin sdkで作った現在時刻よりも60分プラスのtimestamp
const datetimeAdmin = makeTimestamp('admin', 60);

// client sdkで作った現在時刻より-120分プラスのtimestamp
const datetimeClient = makeTimestamp('client', -120);

Discussion