firebase/firebase-js-sdk

FIRESTORE (7.14.3) INTERNAL ASSERTION FAILED: value must be undefined or Uint8Array

rodrigoehlers opened this issue · 15 comments

Describe your environment

  • Operating System version: macOS Catalina Version 10.15.4
  • Browser version: None
  • Node.js version: 12.5.0
  • Firebase SDK version: 7.14.4 (implicitly through latest @firebase/testing)
  • Firebase Testing SDK version: 0.19.4
  • Firebase Product: firestore and testing
  • Jest version: 26.0.1

Describe the problem

Steps to reproduce:

Use the code below, then run tests with jest <path-to-your-file>. In my case <path-to-your-file> was ./spec/basic.spec.js.

It seems that firebase.assertFails(...) throws an error and never returns. Jest doesn't exit.

You'll receive the following logs:

(node:18117) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
 FAIL  spec/basic.spec.js
  Minimal test
    ✕ this tests throws an error and never returns (286 ms)

  ● Minimal test › this tests throws an error and never returns

    FIRESTORE (7.14.3) INTERNAL ASSERTION FAILED: value must be undefined or Uint8Array

      at fail (node_modules/@firebase/firestore/src/util/assert.ts:39:9)
      at hardAssert (node_modules/@firebase/firestore/src/util/assert.ts:53:5)
      at JsonProtoSerializer.fromBytes (node_modules/@firebase/firestore/src/remote/serializer.ts:250:7)
      at JsonProtoSerializer.fromWatchChange (node_modules/@firebase/firestore/src/remote/serializer.ts:431:32)
      at PersistentListenStream.onMessage (node_modules/@firebase/firestore/src/remote/persistent_stream.ts:568:41)
      at node_modules/@firebase/firestore/src/remote/persistent_stream.ts:448:21
      at node_modules/@firebase/firestore/src/remote/persistent_stream.ts:501:18
      at node_modules/@firebase/firestore/src/util/async_queue.ts:358:14

  console.error
    [2020-05-21T14:11:50.900Z]  @firebase/firestore: Firestore (7.14.3): FIRESTORE (7.14.3) INTERNAL ASSERTION FAILED: value must be undefined or Uint8Array

      at Logger.defaultLogHandler [as _logHandler] (node_modules/@firebase/logger/src/logger.ts:115:57)
      at Logger.error (node_modules/@firebase/logger/src/logger.ts:203:21)
      at logError (node_modules/@firebase/firestore/src/util/log.ts:45:20)
      at fail (node_modules/@firebase/firestore/src/util/assert.ts:34:3)
      at hardAssert (node_modules/@firebase/firestore/src/util/assert.ts:53:5)
      at JsonProtoSerializer.fromBytes (node_modules/@firebase/firestore/src/remote/serializer.ts:250:7)
      at JsonProtoSerializer.fromWatchChange (node_modules/@firebase/firestore/src/remote/serializer.ts:431:32)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        2.203 s
Ran all test suites matching /.\/spec\/basic.spec.js/i.
Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

Relevant Code:

Use the following as firestore.rules:

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if false;
      allow write: if false;
    }
  }
}

Create a minimal test, reading from a collection:

const firebase = require('@firebase/testing');

describe('Minimal test', () => {
  const projectId = 'project-id';
  let db;
  let ref;

  beforeAll(() => {
    db = firebase.initializeTestApp({ projectId }).firestore();
    ref = db.collection('hello-world');
  });

  afterAll(() => Promise.all(firebase.apps().map((app) => app.delete())));

  test('this tests throws an error and never returns', async () => {
    await expect(await firebase.assertFails(ref.get()));
  });
});

What have I tried

Downgrading @firebase/testing to version ^0.15.0 fixed the issue.

When I tested on my real rules and tests however, it seems to have broken the test reports as setValue is not in const compressedTypes = ['mapValue', 'listValue', 'constraintValue'] in the repost .html function buildValueString(...) yet? This is only speculation! It could be that my rules are simply wrong.

The report .html therefore throws the following error a couple of times:

Found invalid expression-return type.

Someone with a similar issue posted on StackOverflow.

Thank you for reporting this issue. I was able to reproduce and created a GitHub repository with my reproduction steps: https://github.com/dconeybe/FirebaseJsBug3096. I will look into this and update this bug with my findings.

Just an update that I am still investigating. I have not found the root cause yet.

gleno commented

Seeing this also. Any workarounds?

The issue seems to be resolved with firebase version @7.14.6 and @firebase/testing version @0.19.6. I just upgraded both, re-ran tests and all my tests passed without issues.

I'm still seeing this with

  • firebase-admin@8.12.1
  • @firebase/testing@0.19.6
  • jest@26.0.1
  • ts-jest@26.0.0

I'm also seeing this, but am using firebase directly not firebase-testing

firebase-admin@8.12.1
firebase@7.14.6
jest@26.0.1
ts-jest@26.0.0

I'm seeing this with firebase@7.14.6 and "@angular/fire": "^5.4.2","firebase-tools": "^7.12.1",
in my angular project

Update: Although my investigation is still in progress, here is what I have so far. It looks like this behavior may be due to a "bug" in Jest. The problem surfaces in the follow code from serializer.ts:

value === undefined || value instanceof Uint8Array

The value object is an instance of Buffer, which is a subclass of Uint8Array; however, the value instanceof Uint8Array is mysteriously evaluating to false, causing the assertion failure.

My hypothesis is that the Jest testing framework is doing something wonky with "realms", an advanced feature that allows for different global memory spaces for different parts of a JavaScript program. This has come up as an issue with Jest before, and there is a potential workaround: jestjs/jest#7780.

It looks like the workaround documented in jestjs/jest#7780 fixes the problem. I've updated my reproduction app, https://github.com/dconeybe/FirebaseJsBug3096, with the workaround steps and a workaround branch to demonstrate it.

Since this issue does not appear to be a bug in the Firebase SDK, but rather a side effect of how Jest executes tests, I am going to close this ticket. Feel free to re-open, however, if you disagree and we can continue the discussion. If you think of it, please report back your success with this or other workarounds.

+1

Just came across this error with Jest as well and the workaround is perfect! Followed the workaround and all my tests worked perfectly without any problems.

For others having the same issue and referring to this, use @dconeybe's workaround and your tests should run without problems again.

Okay, so I'm not sure if this fix applies to other people or just me. But when I was designing my application, I was using Ng/Rx to manage state. I had actions, reducers, and effects designed around Firebase Auth state. I would store user info when a user was authenticated, but ALSO and here's the crucial mistake, I was also storing the credential object that gets passed in after angularFire auth login functions. To be specific I am talking about this credential object: https://firebase.google.com/docs/reference/js/firebase.auth.AuthCredential

Now, it took me 6 hours, I tried all sorts of different package versions, but I finally realized the cause of this error. What happens is if I tried to make or store a copy of the credential object values, it would always lead to all kinds of Internal Firebase errors with INTERNAL ASSERTION FAILED to be only one of them. After removing any code related to reading or writing the values that came from the credential object, the error went away completely.

TLDR; It looks like Firebase has some security code that causes errors when interacting with credential objects. Remove that and the Internal Assertion Failed goes away

Sorry to bring this up again but apparently this solution does not work once you convert your tests into Typescript.

I've tried converting the solution from Javascript into Typescript but it still displayed the error:

(node:2960) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 4)
  console.error
    [2020-06-07T03:58:48.724Z]  @firebase/firestore: Firestore (7.14.5): FIRESTORE (7.14.5) INTERNAL ASSERTION FAILED: value must be undefined or Uint8Array

      at Logger.defaultLogHandler [as _logHandler] (node_modules/@firebase/logger/src/logger.ts:115:57)
      at Logger.error (node_modules/@firebase/logger/src/logger.ts:203:21)
      at logError (node_modules/@firebase/firestore/src/util/log.ts:45:20)
      at fail (node_modules/@firebase/firestore/src/util/assert.ts:34:3)
      at hardAssert (node_modules/@firebase/firestore/src/util/assert.ts:53:5)
      at JsonProtoSerializer.fromBytes (node_modules/@firebase/firestore/src/remote/serializer.ts:250:7)
      at JsonProtoSerializer.fromWatchChange (node_modules/@firebase/firestore/src/remote/serializer.ts:431:32)

Is there something I'm missing? Because I'm pretty sure the solution provided above will work for Typescript but I'm not sure if we need to change some things up.

Edit: I've resolved this problem.

Solution

  1. Create a jest.config.js
  2. Paste in this code
module.exports = {
  testEnvironment: "./__test-utils__/custom-jest-environment.js",
}
  1. Run your tests, it should work already.

@moscoso There's no special code for causing failures related to credential objects. So far the issue we've seen here had to do with Jest breaking globals in such a way that a value in the form of a Uint8Array would not pass a value instanceof Uint8Array test. The resolution discussed so far has been a workaround specific to Jest.

In your first post you listed a number of dependencies and versions but did not include Jest. Was that the complete set of dependencies you're using? If so, you might be experiencing a different issue. If you could post a minimal reproduction along the lines of dconeybe's, we can help you figure out what's going on.

@wilhuff my issue turned out to be completely separate from OP and not having to do with Jest... Maybe there is no special code for that, all I'm saying is that if you try to copy the credential object after a login method from AngularFire auth service, it will lead to INTERNAL ASSERTION FAILED.... I could re-create code if you really need me to, but it's exactly what I said. It's not longer an issue for me, removing the code that copies that credential object fixed it for me. Just letting people know if they stumble upon this thread because of the INTERNAL ASSERTION FAILED message

For those that need jest-environment-jsdom instead of jest-environment-node, I was able to get my tests working with a similar workaround:

  1. Add jest-environment-jsdom as a dev dependency in package.json
  2. Add custom-jest-environment.js containing the following:
'use strict';

/**
 * Correct Jest bug that prevents the Firestore tests from running. More info here:
 * https://github.com/firebase/firebase-js-sdk/issues/3096#issuecomment-637584185
 */

const BrowserEnvironment = require('jest-environment-jsdom');

class MyEnvironment extends BrowserEnvironment {
  constructor(config) {
    super(
      Object.assign({}, config, {
        globals: Object.assign({}, config.globals, {
          Uint32Array: Uint32Array,
          Uint8Array: Uint8Array,
          ArrayBuffer: ArrayBuffer
        })
      })
    );
  }

  async setup() {}

  async teardown() {}
}

module.exports = MyEnvironment;
  1. Add the new custom environment to jest.config.js:
testEnvironment: './test/custom-jest-environment.js'