FIrestore: .add () возвращает массив с большим номером - PullRequest
0 голосов
/ 24 января 2020

Я использую firebase-tools эмулятор для локального тестирования сохранения записи в Cloud Firestore.

$ firebase serve --only functions,firestore
i  firestore: Serving ALL traffic (including WebChannel) on http://localhost:8080
⚠  firestore: Support for WebChannel on a separate port (8081) is DEPRECATED and will go away soon. Please use port above instead.
i  firestore: Emulator logging to firestore-debug.log
⚠  Your requested "node" version "8" doesn't match your global version "12"
✔  functions: Emulator started at http://localhost:5000
✔  firestore: Emulator started at http://localhost:8080

Мой код:

// FirestoreConnection.ts
import {firestore} from "firebase-admin";

export default class FirestoreConnection {
  protected shopDomain: string;
  protected database: firestore.Firestore;

  constructor(shopDomain: string, database: firestore.Firestore) {
    this.shopDomain = shopDomain;
    this.database = database;
  }


   // --------------- Public Methods

  public async createNew(type: RecordTypes, documentData: object): Promise<firestore.DocumentSnapshot|null> {

    try {
      return await this.addDocument(collectionName, documentData);
    }
    catch (e) {
      console.log(e, `=====error createNew()=====`);
      return null;
    }
  }

   // --------------- Protected/Private Methods

  protected async addDocument(collectionName: string, documentData: object): Promise<firestore.DocumentSnapshot|null> {
    try {
      const newlyAddedDocument = await this.database
        .collection(collectionName)
        .add(documentData);

      return await newlyAddedDocument.get();
    }
    catch (e) {
      console.log(e, `=====error addDocument()=====`);
      return null;
    }
  }


// FirestoreConnection.test.ts
import * as firebaseTesting from "@firebase/testing";
import {Logger} from "@firebase/logger";
import {RecordTypes} from "../../../../shared";

import FirestoreConnection from "../FirestoreConnection";


/* * * * * * * * * * * * * * * * * * * * *
                  Setup
* * * * * * * * * * * * * * * * * * * * */

// setup firebase logging
const logClient = new Logger("@firebase/testing");
logClient.log("FirestoreConnection.test.ts");

// --------------- Helpers

const createTestDatabase = (credentials): any => {
  return firebaseTesting
    .initializeTestApp({
      projectId: 'testproject',
      auth: credentials
    })
    .firestore();
};

const nullAllApps = firebaseTesting
  .apps().map(app => app.delete());

const db = new FirestoreConnection('testshopdomain', createTestDatabase(null));


// --------------- Before / After

beforeEach(() => {
});


afterEach(async () => {
  try {
    await Promise.all(nullAllApps);
    await firebaseTesting.clearFirestoreData({
      projectId : "testproject"
    })
  }
  catch (e) {
    console.log(e, `=====error=====`);
  }
});


/* * * * * * * * * * * * * * * * * * * * *
                  Tests
* * * * * * * * * * * * * * * * * * * * */
test("creates new record", async () => {
  try {
    const addedDocument = await db
      .createNew(RecordTypes.globalRule, {
        storeId : "dummystoreid"
        , globalPercent : 40
      });

    expect(addedDocument).toEqual({
      storeId : "dummystoreid"
      , globalPercent : 40
      , badProp : 0
    });
  }
  catch (e) {
    console.log(e, `=====error test("creates new record"=====`);
  }
}, 100000);

Я получаю длинную ошибку при запуске jest. Тысячи строк показывают + 118, или подобное число вместе со свойствами объекта, весь красный текст. Затем трассировка стека в белом тексте.

// terminal 
      +                         116,
      +                         111,
      +                         51,

// continues for thousands of lines...


      +                       ],
      +                       "type": "Buffer",
      +                     },
      +                   ],
      +                   "format": "Protocol Buffer 3 DescriptorProto",
      +                   "type": Object {
      +                     "enumType": Array [],
      +                     "extension": Array [],
      +                     "extensionRange": Array [],
      +                     "field": Array [
      +                       Object {
      +                         "defaultValue": "",
      +                         "extendee": "",
      +                         "jsonName": "",
      +                         "label": "LABEL_OPTIONAL",
      +                         "name": "updateTime",
      +                         "number": 1,
      +                         "oneofIndex": 0,
      +                         "options": null,
      +                         "type": "TYPE_MESSAGE",
      +                         "typeName": "protobuf.Timestamp",
      +                       },
      +                       Object {
      +                         "defaultValue": "",
      +                         "extendee": "",
      +                         "jsonName": "",
      +                         "label": "LABEL_REPEATED",
      +                         "name": "transformResults",
      +                         "number": 2,
      +                         "oneofIndex": 0,
      +                         "options": null,
      +                         "type": "TYPE_MESSAGE",
      +                         "typeName": "Value",
      +                       },
      +                     ],
      +                     "name": "WriteResult",
      +                     "nestedType": Array [],
      +                     "oneofDecl": Array [],
      +                     "options": null,
      +                     "reservedName": Array [],
      +                     "reservedRange": Array [],
      +                   },
      +                 },
      +               },
      +             },
      +             "credentialsProvider": FirebaseCredentialsProvider {
      +               "auth": null,
      +               "changeListener": [Function anonymous],
      +               "currentUser": User {
      +                 "uid": null,
      +               },
      +               "forceRefresh": false,
      +               "receivedInitialUser": true,
      +               "tokenCounter": 1,
      +               "tokenListener": [Function anonymous],
      +             },
      +             "handshakeComplete_": true,
      +             "idleTimer": DelayedOperation {
      +               "asyncQueue": AsyncQueue {
      +                 "_isShuttingDown": false,
      +                 "delayedOperations": Array [
      +                   [Circular],
      +                 ],
      +                 "failure": null,
      +                 "operationInProgress": true,
      +                 "tail": Promise {},
      +                 "timerIdsToSkip": Array [],
      +               },
      +               "catch": [Function bound catch],
      +               "deferred": Deferred {
      +                 "promise": Promise {},
      +                 "reject": [Function anonymous],
      +                 "resolve": [Function anonymous],
      +               },
      +               "op": [Function anonymous],
      +               "removalCallback": [Function anonymous],
      +               "targetTimeMs": 1579867383588,
      +               "then": [Function bound then],
      +               "timerHandle": Timeout {
      +                 "_destroyed": false,
      +                 "_idleNext": TimersList {
      +                   "_idleNext": [Circular],
      +                   "_idlePrev": [Circular],
      +                   "expiry": 79350,
      +                   "id": -9007199254740987,
      +                   "msecs": 60000,
      +                   "priorityQueuePosition": 1,
      +                 },
      +                 "_idlePrev": TimersList {
      +                   "_idleNext": [Circular],
      +                   "_idlePrev": [Circular],
      +                   "expiry": 79350,
      +                   "id": -9007199254740987,
      +                   "msecs": 60000,
      +                   "priorityQueuePosition": 1,
      +                 },
      +                 "_idleStart": 19350,
      +                 "_idleTimeout": 60000,
      +                 "_onTimeout": [Function anonymous],
      +                 "_repeat": null,
      +                 "_timerArgs": undefined,
      +                 Symbol(refed): true,
      +                 Symbol(asyncId): 152,
      +                 Symbol(triggerId): 0,
      +               },
      +               "timerId": "write_stream_idle",
      +             },
      +             "idleTimerId": "write_stream_idle",
      +             "lastStreamToken": Object {
      +               "data": Array [
      +                 49,
      +               ],
      +               "type": "Buffer",
      +             },
      +             "listener": Object {
      +               "onClose": [Function bound ],
      +               "onHandshakeComplete": [Function bound ],
      +               "onMutationResult": [Function bound ],
      +               "onOpen": [Function bound ],
      +             },
      +             "queue": AsyncQueue {
      +               "_isShuttingDown": false,
      +               "delayedOperations": Array [
      +                 DelayedOperation {
      +                   "asyncQueue": [Circular],
      +                   "catch": [Function bound catch],
      +                   "deferred": Deferred {
      +                     "promise": Promise {},
      +                     "reject": [Function anonymous],
      +                     "resolve": [Function anonymous],
      +                   },
      +                   "op": [Function anonymous],
      +                   "removalCallback": [Function anonymous],
      +                   "targetTimeMs": 1579867383588,
      +                   "then": [Function bound then],
      +                   "timerHandle": Timeout {
      +                     "_destroyed": false,
      +                     "_idleNext": TimersList {
      +                       "_idleNext": [Circular],
      +                       "_idlePrev": [Circular],
      +                       "expiry": 79350,
      +                       "id": -9007199254740987,
      +                       "msecs": 60000,
      +                       "priorityQueuePosition": 1,
      +                     },
      +                     "_idlePrev": TimersList {
      +                       "_idleNext": [Circular],
      +                       "_idlePrev": [Circular],
      +                       "expiry": 79350,
      +                       "id": -9007199254740987,
      +                       "msecs": 60000,
      +                       "priorityQueuePosition": 1,
      +                     },
      +                     "_idleStart": 19350,
      +                     "_idleTimeout": 60000,
      +                     "_onTimeout": [Function anonymous],
      +                     "_repeat": null,
      +                     "_timerArgs": undefined,
      +                     Symbol(refed): true,
      +                     Symbol(asyncId): 152,
      +                     Symbol(triggerId): 0,
      +                   },
      +                   "timerId": "write_stream_idle",
      +                 },
      +               ],
      +               "failure": null,
      +               "operationInProgress": true,
      +               "tail": Promise {},
      +               "timerIdsToSkip": Array [],
      +             },
      +             "serializer": JsonProtoSerializer {
      +               "databaseId": DatabaseId {
      +                 "database": "(default)",
      +                 "projectId": "testproject",
      +               },
      +               "options": Object {
      +                 "useProto3Json": false,
      +               },
      +             },
      +             "state": 2,
      +             "stream": StreamBridge {
      +               "closeFn": [Function closeFn],
      +               "sendFn": [Function sendFn],
      +               "wrappedOnClose": [Function anonymous],
      +               "wrappedOnMessage": [Function anonymous],
      +               "wrappedOnOpen": [Function anonymous],
      +             },
      +           },
      +         },
      +         "sharedClientState": MemorySharedClientState {
      +           "localState": LocalClientState {
      +             "activeTargetIds": SortedSet {
      +               "comparator": [Function primitiveComparator],
      +               "data": SortedMap {
      +                 "comparator": [Function primitiveComparator],
      +                 "root": LLRBEmptyNode {
      +                   "size": 0,
      +                 },
      +               },
      +             },
      +           },
      +           "onlineStateHandler": [Function sharedClientStateOnlineStateChangedHandler],
      +           "queryState": Object {
      +             "2": "current",
      +           },
      +           "sequenceNumberHandler": null,
      +           "syncEngine": [Circular],
      +         },
      +         "syncEngineListener": EventManager {
      +           "onlineState": 1,
      +           "queries": ObjectMap {
      +             "inner": Object {},
      +             "mapKeyFn": [Function anonymous],
      +           },
      +           "snapshotsInSyncListeners": Set {},
      +           "syncEngine": [Circular],
      +         },
      +       },
      +     },
      +     "_persistenceKey": "app-1579867322226-0.11944467708511985",
      +     "_queue": AsyncQueue {
      +       "_isShuttingDown": false,
      +       "delayedOperations": Array [
      +         DelayedOperation {
      +           "asyncQueue": [Circular],
      +           "catch": [Function bound catch],
      +           "deferred": Deferred {
      +             "promise": Promise {},
      +             "reject": [Function anonymous],
      +             "resolve": [Function anonymous],
      +           },
      +           "op": [Function anonymous],
      +           "removalCallback": [Function anonymous],
      +           "targetTimeMs": 1579867383588,
      +           "then": [Function bound then],
      +           "timerHandle": Timeout {
      +             "_destroyed": false,
      +             "_idleNext": TimersList {
      +               "_idleNext": [Circular],
      +               "_idlePrev": [Circular],
      +               "expiry": 79350,
      +               "id": -9007199254740987,
      +               "msecs": 60000,
      +               "priorityQueuePosition": 1,
      +             },
      +             "_idlePrev": TimersList {
      +               "_idleNext": [Circular],
      +               "_idlePrev": [Circular],
      +               "expiry": 79350,
      +               "id": -9007199254740987,
      +               "msecs": 60000,
      +               "priorityQueuePosition": 1,
      +             },
      +             "_idleStart": 19350,
      +             "_idleTimeout": 60000,
      +             "_onTimeout": [Function anonymous],
      +             "_repeat": null,
      +             "_timerArgs": undefined,
      +             Symbol(refed): true,
      +             Symbol(asyncId): 152,
      +             Symbol(triggerId): 0,
      +           },
      +           "timerId": "write_stream_idle",
      +         },
      +       ],
      +       "failure": null,
      +       "operationInProgress": true,
      +       "tail": Promise {},
      +       "timerIdsToSkip": Array [],
      +     },
      +     "_settings": FirestoreSettings {
      +       "cacheSizeBytes": 41943040,
      +       "credentials": undefined,
      +       "forceLongPolling": false,
      +       "host": "localhost:8080",
      +       "ssl": false,
      +       "timestampsInSnapshots": true,
      +     },
      +   },
      +   "_fromCache": false,
      +   "_hasPendingWrites": false,
      +   "_key": DocumentKey {
      +     "path": ResourcePath {
      +       "len": 2,
      +       "offset": 0,
      +       "segments": Array [
      +         "globalRule",
      +         "YaGhEFEv3kFI0uUWWsSQ",
      +       ],
      +     },
      +   },
// text turns from red to white exactly here (including } )
        }
          at /home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:73:27
          at step (/home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:33:23)
          at Object.next (/home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:14:53)
          at fulfilled (/home/owner/PhpstormProjects/shopify/buyUsedServer/functions/src/classes/__tests__/FirestoreConnection.test.ts:5:58) {
        matcherResult: {
          actual: DocumentSnapshot {
            _firestore: [Firestore],
            _key: [DocumentKey],
            _document: [Document],
            _fromCache: false,
            _hasPendingWrites: false,
            _converter: undefined
          },
          expected: { storeId: 'dummystoreid', globalPercent: 40, badProp: 0 },
          message: [Function],
          name: 'toEqual',
          pass: false
        }
      } =====error test("creates new record"=====


Может кто-нибудь сказать, что является причиной того, что catch возвращает здесь ошибку? Отсутствие сообщения об ошибке затрудняет отладку для меня.

1 Ответ

2 голосов
/ 24 января 2020

В вашем методе addDocument() выделяются несколько моментов:

  1. Сначала вы добавляете документ, а затем сразу же выполняете get() для документа. Это приведет к ненужному запуску чтения из базы данных для получения той же информации, которую вы только что предоставили.
  2. Это get() возвращает DocumentSnapshot , который является контейнерным объектом для значения документа Firestore. Распечатка его необработанного содержимого, вероятно, включает в себя все виды вещей, которые вам не нужны.

Ваш тест на самом деле не тестирует ваши собственные логи c, это, по сути, тестирование Firestore SDK (который уже достаточно хорошо проверено!). Вам может потребоваться вернуть объект данных со вставленным в него идентификатором документа Firestore. Это может выглядеть примерно так:

 async function addDocument(collectionName: string, documentData: object): Promise<object|null> {
    try {
      const newDocRef = await this.database
        .collection(collectionName)
        .add(documentData);

      return Object.assign({}, documentData, {
        __id__: newDocRef.id
      });
    }
    catch (e) {
      console.log(e, `=====error addDocument()=====`);
      return null;
    }
  }

Затем вы можете написать тест, чтобы убедиться, что в результирующем объекте установлено поле __id__.

...